本文出自 Eddy Wiki ,轉載請注明出處:http://eddy.wiki/interview-code.html
本文收集整理了 Android 面試中會遇到的編程算法題。
推薦幾個做編程題目的網(wǎng)站:
下列題目來源:
Mr-YangCheng/ForAndroidInterview — 該處題目應該也是來源于劍指 Offer,不過每題都有詳細思路和解法,值得一看。
二維數(shù)組中查找
題目:
在一個二維數(shù)組中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數(shù),輸入這樣的一個二維數(shù)組和一個整數(shù),判斷數(shù)組中是否含有該整數(shù)。
分析:
根據(jù)題意畫了個簡單的圖,首先我們要確定查找開始的位置,因為它是二維的數(shù)組,它的下標變化是兩個方向的,根據(jù)四個邊界點來分析。
a b
? c d
A:向下 增 ,向右 增
B:向左 減 ,向下 增
C:向上 減 ,向右 增
D:向左 減 ,向上 減
可以看出從B 或C點開始查找剛好可以構建一個二叉查找樹。
二叉查找樹(Binary Search Tree),(又:二叉搜索樹,二叉排序樹)它或者是一棵空樹,或者是具有下列性質的二叉樹: 若它的左子樹不空,則左子樹上所有結點的值均小于它的根結點的值; 若它的右子樹不空,則右子樹上所有結點的值均大于它的根結點的值; 它的左、右子樹也分別為二叉排序樹。
先確定二維數(shù)組的行數(shù)和列數(shù),把 查找值 與 二叉查找樹的根節(jié)點(B或者 C)開始比較,如果相等返回true,小于查找左子樹,大于就查找右子樹。如果遍歷超過數(shù)組邊界,就返回 false。
以C為根節(jié)點查找
public class Solution {
public boolean Find(int [][] array,int target) {
int i = array.length -1;
int m = array[0].length -1;
int j = 0;
while(i>=0 && j<=m){
if(target == array[i][j]){
return true;
}else if(target >array[i][j]){
j++;
}else{
i--;
}
}
return false;
}
}
以B為根節(jié)點查找
public class Solution {
public boolean Find(int [][] array,int target) {
int i = array.length -1;
int j = array[0].length -1;
int n = 0;
while(j>=0 && n<=i){
if(target == array[n][j]){
return true;
}else if(target >array[n][j]){
n++;
}else{
j--;
}
}
return false;
}
}
輸入一個整數(shù),輸出該數(shù)二進制表示中1的個數(shù)。其中負數(shù)用補碼表示。
題目分析
解法一 運行時間:29m 占用內存:629k
public int NumberOf1(int n) {
String s=Integer.toBinaryString(n);
char[] c=s.toCharArray();
int j=0;
for(int i=0;i<c.length;i++){
if(c[i]=='1'){
j++;
}
}
return j;
}
}
解析:
public static String toBinaryString(int i)
以二進制(基數(shù) 2)無符號整數(shù)形式返回一個整數(shù)參數(shù)的字符串表示形式。
①先把整數(shù)轉換成二進制字符串
②把字符串轉換成字符數(shù)組
③遍歷該數(shù)組,判斷每位是否為1,為1 計數(shù)加1。
④遍歷完成返回1的個數(shù)
解法二 運行時間:30ms 占用內存:629k
public class Solution {
public int NumberOf1(int n) {
int count =0;
while(n!=0){
count++;
n = n&(n-1);
}
return count;
}
}
解析:
如果一個整數(shù)不為0,那么這個整數(shù)至少有一位是1。如果我們把這個整數(shù)減1,那么原來處在整數(shù)最右邊的1就會變?yōu)?,原來在1后面的所有的0都會變成1(如果最右邊的1后面還有0的話)。其余所有位將不會受到影響。
舉個例子:
①二進制數(shù) 1100
② 減1后,得 1011
③1100&1011=1000
對比①和③你會發(fā)現(xiàn),把一個整數(shù)減去1,再和原整數(shù)做與運算,會把該整數(shù)最右邊一個1變成0.那么一個整數(shù)的二進制有多少個1,就可以進行多少次這樣的操作。
定義棧的數(shù)據(jù)結構,請在該類型中實現(xiàn)一個能夠得到棧最小元素的min函數(shù)。
import java.util.Stack;
public class Solution {
Stack<Integer> stack = new Stack<Integer>();
Stack<Integer> min = new Stack<Integer>();
public void push(int node) {
stack.push(node);
if(min.isEmpty()){
min.push(node);
}else{
if (node <= min.peek()) {
min.push(node);
}
}
}
public void pop() {
if (stack.peek() == min.peek()) {
min.pop();
}
stack.pop();
}
public int top() {
return stack.peek();
}
public int min() {
return min.peek();
}
}
請實現(xiàn)一個函數(shù),將一個字符串中的空格替換成“%20”。例如,當字符串為We Are Happy.則經(jīng)過替換之后的字符串為We%20Are%20Happy。
輸入描述
str 是StringBuffer的對象
輸出描述
返回替換后的字符串的String對象
題目分析
首先要對輸入?yún)?shù)進行判空和越界判斷(任何算法題都要注意)。
解法一 (運行時間:36ms 占用內存:688k)
public class Solution {
public String replaceSpace(StringBuffer str) {
if(str==null){
return null;
}
StringBuffer sb = new StringBuffer();
char [] strChar = str.toString().toCharArray();
for(int i=0;i<strChar.length;i++){
if(strChar[i]==' '){
sb.append("%20");
}else{
sb.append(strChar[i]);
}
}
return sb.toString();
}
}
把傳入的StringBuffer 對象 str 轉換成字符串 再轉換成字符數(shù)組,申請一個新的StringBuffer 對象 sb來存最后的結果,遍歷整個字符數(shù)組,如果當前字符為空格則把 "%20"加入到sb中,否則直接追加當前字符,最后把sb轉換成字符串返回。
解法二 (運行時間:34ms 占用內存:503k)
public class Solution {
public String replaceSpace(StringBuffer str) {
if(str == null){
return null;
}
for(int i =0;i<str.length();i++){
char c = str.charAt(i);
if(c==' '){
str.replace(i,i+1,"%20");
}
}
return str.toString();
}
}
public StringBuffer replace(int start, int end, String str)
使用給定 String 中的字符替換此序列的子字符串中的字符。該子字符串從指定的 start 處開始,一直到索引 end - 1 處的字符,如果不存在這種字符,則一直到序列尾部。先將子字符串中的字符移除,然后將指定的 String 插入 start。(如果需要,序列將延長以適應指定的字符串。)參數(shù):start - 起始索引(包含)。end - 結束索引(不包含)。str - 將替換原有內容的字符串。返回:此對象。
源碼(可以看出它調用的是父類的方法)
public synchronized StringBuffer More ...replace(int start, int end, String str){
toStringCache = null;
super.replace(start, end, str);
return this;
}
父類AbstractStringBuilder
public AbstractStringBuilder More ...replace(int start, int end, String str) {
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (start > count)
throw new StringIndexOutOfBoundsException("start > length()");
if (start > end)
throw new StringIndexOutOfBoundsException("start > end");
if (end > count)
end = count;
int len = str.length();
int newCount = count + len - (end - start);
ensureCapacityInternal(newCount);
System.arraycopy(value, end, value, start + len, count - end);
str.getChars(value, start);
count = newCount;
return this;
}
可以看出StringBuffer類 提供了 插入的函數(shù),值得注意的是 StringBuilder 也是繼承于AbstractStringBuilder這個抽象類的。所以它也有這個方法,但是String并沒有。
解法三(不推薦,運行時間:38ms 占用內存:654k)
public class Solution {
public String replaceSpace(StringBuffer str) {
return str.toString().replaceAll("\\s", "%20");
}
}
輸入一個字符串,按字典序打印出該字符串中字符的所有排列。例如輸入字符串abc,則打印出由字符a,b,c所能排列出來的所有字符串abc,acb,bac,bca,cab和cba。 結果請按字母順序輸出。
輸入描述
輸入一個字符串,長度不超過9(可能有字符重復),字符只包括大小寫字母。
輸出描述
順序輸出字符串的所有排列
題目分析
這是一個字符串全排列的問題,把全部序列存在TreeSet中默認可得到字典順序。
TreeSet 基于TreeMap實現(xiàn)的SortedSet,可以對Set集合中的元素進行排序,排序后按升序排列元素(缺省是按照自然排序),非線程安全。
思路:
固定一個字符串之后,之后再將問題變小,只需求出后面子串的排列個數(shù)就可以得出結果,然后依次將后面的字符串與前面的交換,再遞歸子串的排列結果,最后當所有字符都固定結束遞歸。
下面這張圖很清楚的給出了遞歸的過程:
解法 運行時間:131ms 占用內存:1477k
import java.util.*;
public class Solution {
//用于最后返回結果
ArrayList<String> list = new ArrayList<>();
//遍歷的時候來存儲序列實現(xiàn)排序
TreeSet<String> set = new TreeSet<>();
public ArrayList<String> Permutation(String str) {
if(str==null || str.length()==0) return list;
Permutation(str.toCharArray(),0);
//容器轉換,TreeSet中的元素已經(jīng)是按照字母順序排序,所以這里做了排序
list.addAll(set);
return list;
}
public void Permutation(char[] s,int index){
if(s==null ||s.length==0 || index<0 || index>s.length-1) return ;
if(index==s.length-1){//遞歸固定到最后一個位置,把該串加入集合
// set不能添加重復元素,所以這里的add()解決了有重復字符的問題
set.add(new String(s));
}else{//固定前index+1個字符,遞歸后面所有可能的子串
for(int i = index;i<s.length;i++){
swap(s,index,i);//交換一次形成一個子串
Permutation(s,index+1);
swap(s,i,index);//復原使下次循環(huán)產生下一個子串
}
}
}
public void swap(char[] s,int i,int j){
char temp = s[i];
s[i] = s[j];
s[j] = temp;
}
}
數(shù)組中有一個數(shù)字出現(xiàn)的次數(shù)超過數(shù)組長度的一半,請找出這個數(shù)字。例如輸入一個長度為9的數(shù)組{1,2,3,2,2,2,5,4,2}。由于數(shù)字2在數(shù)組中出現(xiàn)了5次,超過數(shù)組長度的一半,因此輸出2。如果不存在則輸出0。
思路一:數(shù)組排序后,如果符合條件的數(shù)存在,則一定是數(shù)組中間那個數(shù)。(比如:1,2,2,2,3;或2,2,2,3,4;或2,3,4,4,4等等)
這種方法雖然容易理解,但由于涉及到快排sort,其時間復雜度為O(NlogN)并非最優(yōu);
參考代碼如下:
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers)
{
// 因為用到了sort,時間復雜度O(NlogN),并非最優(yōu)
if(numbers.empty()) return 0;
sort(numbers.begin(),numbers.end()); // 排序,取數(shù)組中間那個數(shù)
int middle = numbers[numbers.size()/2];
int count=0; // 出現(xiàn)次數(shù)
for(int i=0;i<numbers.size();++i)
{
if(numbers[i]==middle) ++count;
}
return (count>numbers.size()/2) ? middle : 0;
}
};
思路二:如果有符合條件的數(shù)字,則它出現(xiàn)的次數(shù)比其他所有數(shù)字出現(xiàn)的次數(shù)和還要多。
在遍歷數(shù)組時保存兩個值:一是數(shù)組中一個數(shù)字,一是次數(shù)。遍歷下一個數(shù)字時,若它與之前保存的數(shù)字相同,則次數(shù)加1,否則次數(shù)減1;若次數(shù)為0,則保存下一個數(shù)字,并將次數(shù)置為1。遍歷結束后,所保存的數(shù)字即為所求。然后再判斷它是否符合條件即可。
參考代碼如下:
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers)
{
if(numbers.empty()) return 0;
// 遍歷每個元素,并記錄次數(shù);若與前一個元素相同,則次數(shù)加1,否則次數(shù)減1
int result = numbers[0];
int times = 1; // 次數(shù)
for(int i=1;i<numbers.size();++i)
{
if(times == 0)
{
// 更新result的值為當前元素,并置次數(shù)為1
result = numbers[i];
times = 1;
}
else if(numbers[i] == result)
{
++times; // 相同則加1
}
else
{
--times; // 不同則減1
}
}
// 判斷result是否符合條件,即出現(xiàn)次數(shù)大于數(shù)組長度的一半
times = 0;
for(int i=0;i<numbers.size();++i)
{
if(numbers[i] == result) ++times;
}
return (times > numbers.size()/2) ? result : 0;
}
};
斐波那契數(shù)列,現(xiàn)在要求輸入一個整數(shù)n,請你輸出斐波那契數(shù)列的第n項。
輸入描述
一個整數(shù)n
輸出描述
斐波那契數(shù)列的第n項。
題目分析
什么是斐波那契數(shù)列?
斐波那契數(shù)列(Fibonacci sequence),又稱黃金分割數(shù)列
在數(shù)學上,斐波納契數(shù)列以如下被以遞歸的方法定義:F(0)=0,F(xiàn)(1)=1,F(xiàn)(n)=F(n-1)+F(n-2)(n≥2,n∈N*)
指的是這樣一個數(shù)列:0、1、1、2、3、5、8、13、21、34、……
遞歸?
看到這個的第一想法就是用遞歸,當n<2時返回n,n>=2時返回f(n-1)+f(n-2), 于是就來試一下...
public class Solution {
public int Fibonacci(int n) {
if(n<2){
return n;
}
return Fibonacci(n-1)+Fibonacci(n-2);
}
}
運行超時:您的程序未能在規(guī)定時間內運行結束,請檢查是否循環(huán)有錯或算法復雜度過大。
為什么呢?
因為重復計算,比如:
f(4) = f(3) + f(2);
= f(2) + f(1) + f(1) + f(0);
= f(1) + f(0) + f(1) + f(1) + f(0);
求f(4)就要計算三次f(1)和兩次f(0),顯然這是不行的。
解法 (動態(tài)規(guī)劃)
運行時間:27ms 占用內存:629k
public class Solution {
public int Fibonacci(int n) {
int i = 0, j = 1;
for(;n>0;n--){
j += i;
i = j-i;
}
return i;
}
}
public class Solution {
public int Fibonacci(int n) {
int a=1,b=1,c=0;
if(n<0){
return 0;
}else if(n==1||n==2){
return 1;
}else{
for (int i=3;i<=n;i++){
c=a+b;
b=a;
a=c;
}
return c;
}
}
}
根據(jù)n的大小,從f(0)=i 和 f(1)=j 從頭開始遍歷整個序列
有f(n)=f(n-1)+f(n-2) (n≥2,n∈N*)
j+=i, 使j成為新的f(n-1)i = j-i ,使i成為f(n-2)
完成后,返回 f(n-2)
注意:java中
while(n--)
會報編譯錯誤:
required: booleanfound: int
一只青蛙一次可以跳上1級臺階,也可以跳上2級……它也可以跳上n級。求該青蛙跳上一個n級的臺階總共有多少種跳法
該題答案和上題一樣,其實就是斐波那契數(shù)列
題目分析
- 設n階的跳數(shù)為f(n)
- 當n=1時,f(1) = 1
- 當n=2時,分為最后一步 跳2階和跳1階 兩種情況,有f(2)=f(0)+f(1)=1+1=2
- 當n=3時,分為最后一步 跳3階、跳2階和跳1階 三種情況,有f(3)=f(0)+f(1)+f(2)=1+1+2=4
- 有 f(n) = f(n-1)+f(n-2)+...+f(1) + f(0)成立同時有 f(n-1)=f(n-2)+...+f(1) + f(0) 成立,可得出f(n)=2f(n-1) (n>=2)
很明顯可以得出遞推公式:
| 1 (n=0 ) f(n) = | 1 (n=1 ) | 2*f(n-1) (n>=2)
解法一 運行時間:35ms 占用內存:654k
public class Solution {
public int JumpFloorII(int target) {
if(target<=1) return 1;
return 2*JumpFloorII(target-1);
}
}
解法二 運行時間:34ms 占用內存:654k
public class Solution {
public int JumpFloorII(int target) {
if(target<=1) return 1;
return 1<<(target-1);
}
}
換一種思路想一下:一共有n個臺階,最后一個臺階是一定要跳上去的,其他的 n-1個可跳可不跳,一共有多少總情況?
2(n-1)
這里用移位實現(xiàn)乘法,時間上要快一些!
把一個數(shù)組最開始的若干個元素搬到數(shù)組的末尾,我們稱之為數(shù)組的旋轉。輸入一個非遞減序列的一個旋轉,輸出旋轉數(shù)組的最小元素。例如數(shù)組{3,4,5,1,2}為{1,2,3,4,5}的一個旋轉,該數(shù)組的最小值為1。
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
if(array.length==0){
return 0;
}
for(int i =0;i<array.length-2;i++){
if(array[i+1] < array[i]){
return array[i+1];
}
}
return array[0];
}
}
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
// 二分法
int low = 0 ; int high = array.length - 1;
while(low < high){
int mid = low + (high - low) / 2;
if(array[mid] > array[high]){
low = mid + 1;
}else if(array[mid] == array[high]){
high = high - 1;
}else{
high = mid;
}
}
return array[low];
}
}
我們可以用 2 * 1 的小矩形橫著或者豎著去覆蓋更大的矩形。請問用n個2 * 1的小矩形無重疊地覆蓋一個2 * n的大矩形,總共有多少種方法?
輸入描述
一個大矩形
輸出描述
覆蓋的方法數(shù)
題目分析
設 被n個2*1的小矩形無重疊地覆蓋的方法總數(shù)為 f(n)
- 當n=1時,明顯f(1)=1;
- 當n=2時,只能兩個都橫著或兩個都豎著放,有f(2)=2;
- 當小矩形個數(shù)為n,來覆蓋這個2*n的大矩形。第一步只有兩種放法:
①豎著放,那么剩下的擺放總數(shù)為 f(n-1)
②橫著放,那么剩下的擺放總數(shù)為 f(n-2)。因為它下面的那塊也跟隨著它的擺放而確定(必須是一個橫著放的小矩形)。
很容易看出滿足斐波那契數(shù)列。
斐波那契數(shù)列(Fibonacci sequence),又稱黃金分割數(shù)列
在數(shù)學上,斐波納契數(shù)列以如下被以遞歸的方法定義:F(0)=0,F(xiàn)(1)=1,F(xiàn)(n)=F(n-1)+F(n-2)(n≥2,n∈N*)
指的是這樣一個數(shù)列:0、1、1、2、3、5、8、13、21、34、……
可以得出遞推公式:
| 1 (n=0 ) f(n) = | 1 (n=1 ) | f(n-1)+f(n-2) (n>=2)
解法一 (遞歸) 運行時間:924ms 占用內存:654k
public class Solution {
public int RectCover(int target) {
if(target<=1) return 1;
return RectCover(target-1)+RectCover(target-2);
}
}
遞歸效率不高,重復計算多,比如:
f(4) = f(3) + f(2);
= f(2) + f(1) + f(1) + f(0);
= f(1) + f(0) + f(1) + f(1) + f(0);
求f(4)就要計算三次f(1)和兩次f(0),顯然這是不行的。
解法二(動態(tài)規(guī)劃) 運行時間:29ms 占用內存:629k
public class Solution {
public int RectCover(int target) {
if(target<=1) return 1;
int i =1;//f(0)
int j =1;//f(1)
for(;target>=2;target--){
j+=i;
i=j-i;
}
return j;
}
}
顯然這個快很多,n>=2時,根據(jù) f(n)=f(n-1)+f(n-2)進行依次計算,最后得出 f(target)并返回。
輸入一個整數(shù)數(shù)組,實現(xiàn)一個函數(shù)來調整該數(shù)組中數(shù)字的順序,使得所有的奇數(shù)位于數(shù)組的前半部分,所有的偶數(shù)位于位于數(shù)組的后半部分,并保證奇數(shù)和奇數(shù),偶數(shù)和偶數(shù)之間的相對位置不變。
解法二 運行時間:27ms 占用內存:503k
public class Solution {
public void reOrderArray(int [] array) {
if(array.length==0 || array==null){
return;
}
for(int i=0;i<array.length-1;i++){
for(int j=0;j<array.length-i-1;j++){
if(array[j]%2==0 && array[j+1]%2==1){
int temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
}
}
}
}
類似于冒泡排序,以為用到array[j+1]數(shù)組不要越界。
有一個容器類 ArrayList,保存整數(shù)類型的元素,現(xiàn)在要求編寫一個幫助類,類內提供一個幫助函數(shù),幫助函數(shù)的功能是刪除 容器中<10的元素。
LeetCode上股票利益最大化問題
劍指offer上第一次只出現(xiàn)一次的字符
反轉字符串
字符串中出現(xiàn)最多的字符。
實現(xiàn)stack 的pop和push接口 要求:
- 1.用基本的數(shù)組實現(xiàn)
- 2.考慮范型
- 3.考慮下同步問題
- 4.考慮擴容問題
求素數(shù)
單例模式——寫一個Singleton出來
參考:http://yuweiguocn.github.io/java-instance/
七種方式實現(xiàn)Singleton模式
public class Test {
/**
* 單例模式,懶漢式,線程安全
*/
public static class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return INSTANCE;
}
}
/**
* 單例模式,懶漢式,線程不安全
*/
public static class Singleton2 {
private static Singleton2 instance = null;
private Singleton2() {
}
public static Singleton2 getInstance() {
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
}
/**
* 單例模式,餓漢式,線程安全,多線程環(huán)境下效率不高
*/
public static class Singleton3 {
private static Singleton3 instance = null;
private Singleton3() {
}
public static synchronized Singleton3 getInstance() {
if (instance == null) {
instance = new Singleton3();
}
return instance;
}
}
/**
* 單例模式,懶漢式,變種,線程安全
*/
public static class Singleton4 {
private static Singleton4 instance = null;
static {
instance = new Singleton4();
}
private Singleton4() {
}
public static Singleton4 getInstance() {
return instance;
}
}
/**
* 單例模式,使用靜態(tài)內部類,線程安全(推薦)
*/
public static class Singleton5 {
private final static class SingletonHolder {
private static final Singleton5 INSTANCE = new Singleton5();
}
private static Singleton5 getInstance() {
return SingletonHolder.INSTANCE;
}
}
/**
* 靜態(tài)內部類,使用枚舉方式,線程安全(推薦)
*/
public enum Singleton6 {
INSTANCE;
public void whateverMethod() {
}
}
/**
* 靜態(tài)內部類,使用雙重校驗鎖,線程安全(推薦)
*/
public static class Singleton7 {
private volatile static Singleton7 instance = null;
private Singleton7() {
}
public static Singleton7 getInstance() {
if (instance == null) {
synchronized (Singleton7.class) {
if (instance == null) {
instance = new Singleton7();
}
}
}
return instance;
}
}
}
二叉樹遍歷
重建二叉樹
題目:
輸入二叉樹的前序遍歷和中序遍歷的結果,重建出該二叉樹。假設前序遍歷和中序遍歷結果中都不包含重復的數(shù)字,例如輸入的前序遍歷序列 {1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6}重建出如圖所示的二叉 樹。
解題思路:
前序遍歷第一個結點是父結點,中序遍歷如果遍歷到父結點,那么父結點前面的結點是左子樹的結點,后邊的結點的右子樹的結點,這樣我們可以找到左、右子樹的前序遍歷和中序遍歷,我們可以用同樣的方法去構建左右子樹,可以用遞歸完成。
代碼:
public class BinaryTreeNode {
public static int value;
public BinaryTreeNode leftNode;
public BinaryTreeNode rightNode;
}
public class Solution {
public static BinaryTreeNode constructCore(int[] preorder, int[] inorder) throws Exception {
if (preorder == null || inorder == null) {
return null;
}
if (preorder.length != inorder.length) {
throw new Exception("長度不一樣,非法的輸入");
}
BinaryTreeNode root = new BinaryTreeNode();
for (int i = 0; i < inorder.length; i++) {
if (inorder[i] == preorder[0]) {
root.value = inorder[i];
System.out.println(root.value);
root.leftNode = constructCore(Arrays.copyOfRange(preorder, 1, i + 1),
Arrays.copyOfRange(inorder, 0, i));
root.rightNode = constructCore(Arrays.copyOfRange(preorder, i + 1, preorder.length),
Arrays.copyOfRange(inorder, i + 1, inorder.length));
}
}
return root;
}
}
數(shù)值的整數(shù)次方
題目:
實現(xiàn)函數(shù)double Power(double base,int exponent),求base的exponent次方,不得使用庫函數(shù),同時不需要考慮大數(shù)問題。
看到了很多人會這樣寫:
public static double powerWithExponent(double base,int exponent){
double result = 1.0;
for(int i = 1; i <= exponent; i++){
result = result * base;
}
return result;
}
輸入的指數(shù)(exponent)小于1即是零和負數(shù)時怎么辦?
當指數(shù)為負數(shù)的時候,可以先對指數(shù)求絕對值,然后算出次方的結果之后再取倒數(shù),當?shù)讛?shù)(base)是零且指數(shù)是負數(shù)的時候,如果不做特殊處理,就會出現(xiàn)對0求倒數(shù)從而導致程序運行出錯。最后,由于0的0次方在數(shù)學上是沒有意義的,因此無論是輸出0還是1都是可以接受的。
public double power(double base, int exponent) throws Exception {
double result = 0.0;
if (equal(base, 0.0) && exponent < 0) {
throw new Exception("0的負數(shù)次冪無意義");
}
if (equal(exponent, 0)) {
return 1.0;
}
if (exponent < 0) {
result = powerWithExponent(1.0 / base, -exponent);
} else {
result = powerWithExponent(base, exponent);
}
return result;
}
private double powerWithExponent(double base, int exponent) {
double result = 1.0;
for (int i = 1; i <= exponent; i++) {
result = result * base;
}
return result;
}
// 判斷兩個double型數(shù)據(jù),計算機有誤差
private boolean equal(double num1, double num2) {
if ((num1 - num2 > -0.0000001) && (num1 - num2 < 0.0000001)) {
return true;
} else {
return false;
}
}
一個細節(jié),再判斷底數(shù)base是不是等于0時,不能直接寫base==0,這是因為在計算機內表示小數(shù)時(包括float和double型小數(shù))都有誤差。判斷兩個數(shù)是否相等,只能判斷 它們之間的絕對值是不是在一個很小的范圍內。如果兩個數(shù)相差很小,就可以認為它們相等。
還有更快的方法。
如果我們的目標是求出一個數(shù)字的32次方,如果我們已經(jīng)知道了它的16次方,那么只要在16次方的基礎上再平方一次就好了,依此類推,我們求32次方只需要做5次乘法。
我們可以利用如下公式:
private double powerWithExponent2(double base,int exponent){
if(exponent == 0){
return 1;
}
if(exponent == 1){
return base;
}
double result = powerWithExponent2(base, exponent >> 1);
result *= result;
if((exponent&0x1) == 1){
result *= base;
}
return result;
}
我們用右移運算代替除2,用位與運算符代替了求余運算符(%)來判斷一個數(shù)是奇數(shù)還是偶數(shù)。位運算的效率比乘除法及求余運算的效率要高很多。
撲克牌的順子
題目:
從撲克牌中隨機抽 5 張牌,判斷是不是順子,即這 5 張牌是不是連續(xù)的。 2-10 為數(shù)字本身,A 為 1,J 為 11,Q 為 12,K 為 13,而大小王可以看成任意的 數(shù)字。
解題思路:我們可以把5張牌看成是由5個數(shù)字組成的俄數(shù)組。大小王是特殊的數(shù)字,我們可以把它們都定義為0,這樣就可以和其他的牌區(qū)分開來。
首先把數(shù)組排序,再統(tǒng)計數(shù)組中0的個數(shù),最后統(tǒng)計排序之后的數(shù)組中相鄰數(shù)字之間的空缺總數(shù)。如果空缺的總數(shù)小于或者等于0的個數(shù),那么這個數(shù)組就是連續(xù)的,反之則不連續(xù)。如果數(shù)組中的非0數(shù)字重復出現(xiàn),則該數(shù)組是不連續(xù)的。換成撲克牌的描述方式就是如果一幅牌里含有對子,則不可能是順子。
詳細代碼:
import java.util.Arrays;
public class Solution {
public boolean isContinuous(int[] number){
if(number == null){
return false;
}
Arrays.sort(number);
int numberZero = 0;
int numberGap = 0;
//計算數(shù)組中0的個數(shù)
for(int i = 0;i < number.length&&number[i] == 0; i++){
numberZero++;
}
//統(tǒng)計數(shù)組中的間隔數(shù)目
int small = numberZero;
int big = small + 1;
while(big<number.length){
//兩個數(shù)相等,有對子,不可能是順子
if(number[small] == number[big]){
return false;
}
numberGap+= number[big] - number[small] - 1;
small = big;
big++;
}
return (numberGap>numberZero)?false:true;
}
}
圓圈中最后剩下的數(shù)字
題目
0,1,...,n-1這n個數(shù)字排成一個圓圈,從數(shù)字0開始每次從這個圓圈里刪除第m個數(shù)字。求這個圓圈里剩下的最后一個數(shù)字。
解法:
可以創(chuàng)建一個總共有n個結點的環(huán)形鏈表,然后每次在這個鏈表中刪除第m個結點。我們發(fā)現(xiàn)使用環(huán)形鏈表里重復遍歷很多遍。重復遍歷當然對時間效率有負面的影響。這種方法每刪除一個數(shù)字需要m步運算,總共有n個數(shù)字,因此總的時間復雜度為O(mn)。同時這種思路還需要一個輔助的鏈表來模擬圓圈,其空間復雜度O(n)。接下來我們試著找到每次被刪除的數(shù)字有哪些規(guī)律,希望能夠找到更加高效的算法。
首先我們定義一個關于n和m的方程f(n,m),表示每次在n個數(shù)字0,1,。。。n-1中每次刪除第m個數(shù)字最后剩下的數(shù)字。
在這n個數(shù)字中,第一個被刪除的數(shù)字是(m-1)%n.為了簡單起見,我們把(m-1)%n記為k,那么刪除k之后剩下的n-1個數(shù)字為0,1,。。。。k-1,k+1,.....n-1。并且下一次刪除從數(shù)字k+1,......n-1,0,1,....k-1。該序列最后剩下的數(shù)字也應該是關于n和m的函數(shù)。由于這個序列的規(guī)律和前面最初的序列不一樣(最初的序列是從0開始的連續(xù)序列),因此該函數(shù)不同于前面的函數(shù),即為f'(n-1,m)。最初序列最后剩下的數(shù)字f(n,m)一定是刪除一個數(shù)字之后的序列最后剩下的數(shù)字,即f(n,m)=f'(n-1,m).
接下來我么把剩下的這n-1個數(shù)字的序列k+1,....n-1,0,1,,,,,,,k-1做一個映射,映射的結果是形成一個從0到n-2的序列
k+1 ------> 0
k+2 ---------> 1
。。。。
n-1 ----- > n-k-2
0 -------> n-k-1
1 ---------> n-k
.....
k-1 ---------> n-k
我們把映射定義為p,則p(x) = (x-k-1)%n。它表示如果映射前的數(shù)字是x,那么映射后的數(shù)字是(x-k-1)%n.該映射的逆映射是p-1(x)= (x+k+1)%n.
由于映射之后的序列和最初的序列具有同樣的形式,即都是從0開始的連續(xù)序列,因此仍然可以用函數(shù)f來表示,記為f(n-1,m).根據(jù)我們的映射規(guī)則,映射之前的序列中最后剩下的數(shù)字f'(n-1,m) = p-1[(n-1,m)] = [f(n-1,m)+k+1]%n ,把k= (m-1)%n代入f(n,m) = f'(n-1,m) =[f(n-1,m)+m]%n.
經(jīng)過上面的復雜的分析,我們終于找到一個遞歸的公示。要得到n個數(shù)字的序列中最后剩下的數(shù)字,只需要得到n-1個數(shù)字的序列和最后剩下的數(shù)字,并以此類推。當n-1時,也就是序列中開始只有一個數(shù)字0,那么很顯然最后剩下的數(shù)字就是0.我們把這種關系表示為:
代碼如下:
public static int lastRemaining(int n, int m){
if(n < 1 || m < 1){
return -1;
}
int last = 0;
for(int i = 2; i <= n; i++){
last = (last + m) % i;
}
return last;
}
two-sum
Question
Given an array of integers, find two numbers such that they add up to a specific target number.
The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. Please note that your returned answers (both index1 and index2) are not zero-based.
You may assume that each input would have exactly one solution.
Input: numbers={2, 7, 11, 15}, target=9
Output: index1=1, index2=2
題目大意:
給定一個整數(shù)數(shù)組,找到2個數(shù)字,這樣他們就可以添加到一個特定的目標號。功能twosum應該返回兩個數(shù)字,他們總計達目標數(shù),其中index1必須小于index2。請注意,你的答案返回(包括指數(shù)和指數(shù))不為零的基礎。你可以假設每個輸入都有一個解決方案。
輸入數(shù)字numbers= { 2,7,11,15 },目標= 9輸出:index1 = 1,index2= 2
解題思路:
可以申請額外空間來存儲目標數(shù)減去從頭遍歷的數(shù),記為key,如果hashMap中存在該key,就可以返回兩個索引了。
代碼;
import java.util.HashMap;
public class Solution {
public int[] twoSum(int[] numbers, int target) {
HashMap<Integer, Integer> map = new HashMap<>();
for(int i = 0; i < numbers.length; i++){
if(map.get(numbers[i]) != null){
int[] result = {map.get(numbers[i]) + 1, i+1};
return result;
}else {
map.put(target - numbers[i], i);
}
}
int[] result = {};
return result;
}
}
設計一個有getMin功能的棧
實現(xiàn)一個特殊的棧,在實現(xiàn)棧的基本功能的基礎上,在實現(xiàn)返回棧中最小元素的操作。
要求:
- pop、push、getMin操作的時間復雜度都是O(1)
- 設計的棧類型可以使用現(xiàn)成的棧結構
解題:
package chapter01_stackandqueue;
import java.util.Stack;
/**
*
* 實現(xiàn)一個特殊的棧,在實現(xiàn)棧的基本功能的基礎上,在實現(xiàn)返回棧中最小元素的操作。 要求: 1. pop、push、getMin操作的時間復雜度都是O(1)
* 2. 設計的棧類型可以使用現(xiàn)成的棧結構
*
* @author dream
*
*/
public class Problem01_GetMinStack {
public static class MyStack1 {
/**
* 兩個棧,其中stacMin負責將最小值放在棧頂,stackData通過獲取stackMin的peek()函數(shù)來獲取到棧中的最小值
*/
private Stack<Integer> stackData;
private Stack<Integer> stackMin;
/**
* 在構造函數(shù)里面初始化兩個棧
*/
public MyStack1() {
stackData = new Stack<Integer>();
stackMin = new Stack<Integer>();
}
/**
* 該函數(shù)是stackData彈出棧頂數(shù)據(jù),如果彈出的數(shù)據(jù)恰好等于stackMin的數(shù)據(jù),那么stackMin也彈出
* @return
*/
public Integer pop() {
Integer num = (Integer) stackData.pop();
if (num == getmin()) {
return (Integer) stackMin.pop();
}
return null;
}
/**
* 該函數(shù)是先判斷stackMin是否為空,如果為空,就push新的數(shù)據(jù),如果這個數(shù)小于stackMin中的棧頂元素,那么stackMin需要push新的數(shù),不管怎么樣
* stackData都需要push新的數(shù)據(jù)
* @param value
*/
public void push(Integer value) {
if (stackMin.isEmpty()) {
stackMin.push(value);
}
else if (value < getmin()) {
stackMin.push(value);
}
stackData.push(value);
}
/**
* 該函數(shù)是當stackMin為空的話第一次也得push到stackMin的棧中,返回stackMin的棧頂元素
* @return
*/
public Integer getmin() {
if (stackMin == null) {
throw new RuntimeException("stackMin is empty");
}
return (Integer) stackMin.peek();
}
}
public static void main(String[] args) throws Exception {
/**
* 要注意要將MyStack1聲明成靜態(tài)的,靜態(tài)內部類不持有外部類的引用
*/
MyStack1 stack1 = new MyStack1();
stack1.push(3);
System.out.println(stack1.getmin());
stack1.push(4);
System.out.println(stack1.getmin());
stack1.push(1);
System.out.println(stack1.getmin());
System.out.println(stack1.pop());
System.out.println(stack1.getmin());
System.out.println("=============");
}
}
由兩個棧組成的隊列
題目:
編寫一個類,用兩個棧實現(xiàn)隊列,支持隊列的基本操作(add、poll、peek)。
解題:
/**
*
* 編寫一個類,用兩個棧實現(xiàn)隊列,支持隊列的基本操作(add、poll、peek)。
*
* @author dream
*
*/
public class Problem02_TwoStacksImplementQueue {
public static class myQueue{
Stack<Integer> stack1;
Stack<Integer> stack2;
public myQueue() {
stack1 = new Stack<Integer>();
stack2 = new Stack<Integer>();
}
/**
* add只負責往stack1里面添加數(shù)據(jù)
* @param newNum
*/
public void add(Integer newNum){
stack1.push(newNum);
}
/**
* 這里要注意兩點:
* 1.stack1要一次性壓入stack2
* 2.stack2不為空,stack1絕不能向stack2壓入數(shù)據(jù)
* @return
*/
public Integer poll(){
if(stack1.isEmpty() && stack2.isEmpty()){
throw new RuntimeException("Queue is Empty");
}else if(stack2.isEmpty()){
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
public Integer peek(){
if(stack1.isEmpty() && stack2.isEmpty()){
throw new RuntimeException("Queue is Empty");
}else if(stack2.isEmpty()){
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.peek();
}
}
public static void main(String[] args) {
myQueue mQueue = new myQueue();
mQueue.add(1);
mQueue.add(2);
mQueue.add(3);
System.out.println(mQueue.peek());
System.out.println(mQueue.poll());
System.out.println(mQueue.peek());
System.out.println(mQueue.poll());
System.out.println(mQueue.peek());
System.out.println(mQueue.poll());
}
}
如何僅用遞歸函數(shù)和棧操作逆序一個棧
題目:
一個棧一次壓入了1、2、3、4、5,那么從棧頂?shù)綏5追謩e為5、4、3、2、1.將這個棧轉置后,從棧頂?shù)綏5诪?、2、3、4、5,也就是實現(xiàn)棧中元素的逆序,但是只能用遞歸函數(shù)來實現(xiàn),不能用其他數(shù)據(jù)結構。
解題:
/**
* 一個棧一次壓入了1、2、3、4、5,那么從棧頂?shù)綏5追謩e為5、4、3、2、1.將這個棧轉置后,
* 從棧頂?shù)綏5诪?、2、3、4、5,
* 也就是實現(xiàn)棧中元素的逆序,但是只能用遞歸函數(shù)來實現(xiàn),不能用其他數(shù)據(jù)結構。
* @author dream
*
*/
public class Problem03_ReverseStackUsingRecursive {
public static void reverse(Stack<Integer> stack) {
if (stack.isEmpty()) {
return;
}
int i = getAndRemoveLastElement(stack);
reverse(stack);
stack.push(i);
}
/**
* 這個函數(shù)就是刪除棧底元素并返回這個元素
* @param stack
* @return
*/
public static int getAndRemoveLastElement(Stack<Integer> stack) {
int result = stack.pop();
if (stack.isEmpty()) {
return result;
} else {
int last = getAndRemoveLastElement(stack);
stack.push(result);
return last;
}
}
public static void main(String[] args) {
Stack<Integer> test = new Stack<Integer>();
test.push(1);
test.push(2);
test.push(3);
test.push(4);
test.push(5);
reverse(test);
while (!test.isEmpty()) {
System.out.println(test.pop());
}
}
}