劍指offer(java版)——基礎知識篇

1.二維數組中的查找

題目描述
在一個二維數組中(每個一維數組的長度相同),每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。

public class Solution {
    public boolean Find(int target, int [][] array) {
        if(array==null||array.length==0||array[0].length==0){
            return false;
        }
        int i=0;
        int j=array[0].length-1;
        while(i<array.length&&j>=0){
            if(target==array[i][j]){
                return true;
            }else if(target<array[i][j]){
                j--;
            }else{
                i++;
            }
        }
        return false;
    }
}

2.替換空格

題目描述
請實現一個函數,將一個字符串中的每個空格替換成“%20”。例如,當字符串為We Are Happy.則經過替換之后的字符串為We%20Are%20Happy。

思路1:從頭到尾掃描字符串,遇到空格,將后面的字符串后移2位,然后做替換
缺點:后面的字符串將被移動多次(多次賦值)。如果有O(n)個空格,字符串將移動O(n)次,每次需要移動O(n)個字符,所以時間復雜度是O(n^2)
思路2:從后往前開始字符串的替換和移動。首先遍歷一次字符串,找到所有空格的數目。替換后字符串長度為 原來的字符串長度+空格個數*2。我們新建一個數組,從尾部開始向前遍歷原數組,如果是空格,則新數組的位置替換成“%20”,新數組的下標逐個遞減3格;如果不是空格,直接復制原數組的內容到新數組對應位置,下標都減1格。

注意:
1.空字符串
2.null輸入
3.沒有空格的情況
4.空格在頭,中,尾的情況

知識點:
1.StringBuffer線程安全。內部char[]數組,方法前加synchronized關鍵字
2.StringBuffer中的capacity和length是不同的,capacity是容量,而length是實際字符串長度。當我們new StringBuffer(16)的時候,我們得到的是一個capacity=16,length=0的StringBuffer。
3.返回值是String,StringBuffer需要toString轉化
4.從char[]到String,不能通過toString方式(沒有重寫這個方法,實際使用的是Object的toString,即返回了地址),而通過new String(char[] array)方式
5.StringBuffer的get和set:str.charAt(i)、str.setCharAt(int index,char newChar)
6.String的get和set:str.charAt(i)、String不可變,可以將String轉化成StringBuilder/StringBuffer

public class Solution {
        public static String replaceSpace(StringBuffer str) {
        if(str==null||str.length()<=0){
            return str.toString();
        }
        int count=0;
        for(int i=0;i<str.length();i++){
            if(str.charAt(i)==' '){
                count++;
            }
        }
        char[] newStr = new char[str.length()+count*2];
        int i=str.length()-1;
        int j=newStr.length-1;
        while(i>=0){
            if(str.charAt(i)==' '){
                newStr[j--]='0';
                newStr[j--]='2';
                newStr[j--]='%';
            }else{
                newStr[j--]=str.charAt(i);
            }
            i--;
        }
        return new String(newStr);
    }
}

3.從尾到頭打印鏈表

題目描述
輸入一個鏈表,按鏈表值從尾到頭的順序返回一個ArrayList。
思路一:用棧。先進后出。
要點:
1.注意判斷null值
2.stack的pop和peek方法可能會拋出EmptyStackException(),所以需要先調用empty()判斷是否為空,然后再進行操作

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*
*/
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> result = new ArrayList<Integer>();
        if(listNode==null){
            return result;
        }
        Stack<Integer> stack = new Stack<Integer>();
        ListNode currentNode = listNode;
        while(currentNode!=null){
            stack.push(currentNode.val);
            currentNode = currentNode.next;
        }
        
        while(!stack.empty()){
            result.add(stack.pop());
        }
        return result;
    }
}

思路二:用遞歸的方式。遞歸的終止條件是下一個節點為空指針。
需要注意,如果是遞歸,需要把返回結果的list放在遞歸函數外,以免每次遞歸時被歸0

public class Solution {
    ArrayList<Integer> result = new ArrayList<Integer>();
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        if(listNode==null){
            return result;
        }
        printListFromTailToHead(listNode.next);
        result.add(listNode.val);
        return result;
    }
}

4.重建二叉樹

題目描述
輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重復的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹并返回。
思路:
前序遍歷的首個節點是根節點,接下來是所有左子樹節點,最后是所有右子樹節點。利用中序遍歷得到左子樹節點個數和右子樹節點個數;再對左右子樹分別重復上述步驟,進行迭代。

1.前序遍歷和層次遍歷是不同的。前序遍歷是根左右,深度優先,會遍歷完左子樹再遍歷右子樹;層次遍歷是廣度優先。同層從左到右遍歷。
2.前序遍歷的序列,首個節點是根節點。再之后是全部的左子樹節點;再然后是全部的右子樹節點。所以給出前序遍歷序列之后,我們需要找到左子樹截止的位置;
3.這里可以借助中序遍歷的特點,根節點左邊的是全部的左子樹序列,根節點右邊的是全部的右子樹序列。
4.如果是后序遍歷,則按照左右根的順序,最后一個節點是根節點,根節點前面是右子樹的全部節點,再往前是左子樹的全部節點。也需要找到一個左右子樹的分界點。

如果是前序+中序/后序+中序,可以唯一確定一棵二叉樹。
如果是前序+后序,無法唯一確定一棵二叉樹。
相關問題:
1.二叉樹遍歷(已知前序和后序遍歷,求中序遍歷的可能的序列數)
https://blog.csdn.net/qq_37437983/article/details/79613947
2.已知前序和中序,求后序遍歷
遞歸。后序遍歷 右子節點緊挨根節點(如果有的話);左子節點位置在根節點前,右子樹節點前的位置。
如果,i是后序遍歷數組每次迭代的根節點位置,j是右子樹長度;則i-j-1是左子樹根節點的位置,i-1是右子樹根節點的位置。
https://blog.csdn.net/qq_29375837/article/details/81160362

/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        TreeNode node=construct(pre,in,0,0,in.length-1);
        return node;
    }
    public TreeNode construct(int [] pre,int [] in,int preStart,int inStart,int inEnd){
        TreeNode node=null;
        if(pre==null||in==null||preStart<0||preStart>pre.length||inStart<0||
inEnd>in.length-1||inStart>inEnd){
            return null;
        }
        for(int i=inStart;i<=inEnd;i++){
            if(in[i]==pre[preStart]){
                node = new TreeNode(pre[preStart]);
                node.left = construct(pre,in,preStart+1,inStart,i-1);
                node.right = construct(pre,in,preStart+(i-inStart)+1,i+1,inEnd);
                break;
            }
        }
        return node;
    }
}

5.用兩個棧實現一個隊列

題目描述
用兩個棧來實現一個隊列,完成隊列的Push和Pop操作。 隊列中的元素為int類型。
思路:push不變
pop時先判斷stack2是否為空,不為空直接pop stack2;為空則把stack1的元素逐一出棧再入棧stack2,最后pop stack2
遵循先進先出

import java.util.Stack;

public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
    public void push(int node) {
        stack1.push(node);
    }
    public int pop() {
        if(stack2.empty()){
            while(!stack1.empty()){
                int node = stack1.pop();
                stack2.push(node);
            }
        }
        return stack2.pop();
    }
}

附加:如果是用兩個隊列實現一個棧呢
思路1:queue1做進出隊列,queue2做中轉隊列。push直接push到queue1,pop的時候,先把queue1的全部元素pop到queue2,然后queue2.pop,再把queue2中的所有元素pop回queue1(效率不高)
思路2: queue1和queue2都做進出隊列。
push的時候,選擇非空的隊列進行push,如果都空,選擇queue1
push。
pop的時候,把非空隊列的元素全部pop到空隊列,然后pop。

6.旋轉數組的最小數字

題目描述
把一個數組最開始的若干個元素搬到數組的末尾,我們稱之為數組的旋轉。 輸入一個非減排序的數組的一個旋轉,輸出旋轉數組的最小元素。 例如數組{3,4,5,1,2}為{1,2,3,4,5}的一個旋轉,該數組的最小值為1。 NOTE:給出的所有元素都大于0,若數組大小為0,請返回0。
關鍵點:
1.首先,“若干個元素搬到數組的末尾”中的若干個,可以為0個;即{1,2,3,4,5}也是{1,2,3,4,5}的一個旋轉。
2.可以有相同元素。
10111是01111的一個旋轉
11101也是01111的一個旋轉
思路:
思路1.遍歷得最小:O(n)復雜度
思路2.優化一點。發現后面的元素小于前面元素,則break。O(n)復雜度
思路3:利用二分法。O(logn)復雜度
利用二分法需要考慮縮小的條件。
考慮對于一個非遞減數組有下面的情況:
(1)旋轉0個元素。即旋轉后的數組也非遞減。返回數組第一個元素即可。這時只要判斷出第一個元素小于最后一個元素,必定是這種情況
(2)如果旋轉了,則必定是兩個非遞減數組的拼接。我們用low在第一個非遞減數組中,high在第二個非遞減數組中,不斷縮小邊界。mid是low和high的中間位置。
最小元素位于兩個非遞減數組的分界處。
如果low指向的值小于等于mid指向的值,說明low和mid都位于第一個非遞減數組,low可以后移至mid的位置,依舊還是位于第一個非遞減數組。
如果high指向的值大于等于mid指向的值,說明mid和high都位于第二個非遞減數組,high可以前移至mid的位置,依舊還是位于第二個非遞減數組。
最終,low將指向第一個非遞減數組的最后一個元素,high將指向第二個非遞減數組的第一個元素(即分界處)。這時,low和high將相鄰,這就是循環結束的條件。high指向的位置就是最小元素的位置。
(3)在(2)中還包含著一個特殊情況。比如10111。它的low,high,mid指向的值相同,但是顯然,low不能縮小到mid的邊界。這說明,當low,high,mid指向值相同的時候,我們無法判斷該如何移動指針。這個時候,應該改用遍歷的方式。

import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if(array==null||array.length==0)
            return 0;
        int low = 0;
        int high = array.length-1;
        int mid = low;
        while(array[low]>=array[high]){
            if(high-low==1){
                mid = high;
                break;
            }
            mid = (low+high)/2;
            if(array[low]==array[mid]&&array[mid]==array[high]){
                return inOrder(array,low,high);
            }else if(array[low]<=array[mid]){
                low = mid;
            }else if(array[high]>=array[mid]){
                high = mid;
            }
        }
        return array[mid];
    }
    public int inOrder(int[] array,int low,int high){
        for(int i=low;i<high;i++){
            if(array[i]>array[i+1]){
                return array[i+1];
            }
        }
        return array[high];
    }
}

7.斐波那契數列

用循環,不要用遞歸
注意取值,是否超過int范圍(一般10位)
39是63245986,沒超。
保險起見,可以使用long

public class Solution {
    public int Fibonacci(int n) {
        if(n==0)return 0;
        if(n==1)return 1;
        int[] fib = {0,1};
        int result =0;
        for(int i=2;i<=n;i++){
            result = fib[0]+fib[1];
            fib[0]=fib[1];
            fib[1]=result;
        }
        return result;
    }
}

變種1:跳臺階
一只青蛙一次可以跳上1級臺階,也可以跳上2級,求該青蛙跳上一個n級臺階總共有多少種跳法(先后次序不同算不同的結果)
解法:如果只有一級臺階,有1種跳法;如果有2級,有2種跳法。(初始)
如果有n級臺階(n>2),那么如果第一步跳1,有f(n-1)種跳法;如果第一步跳2,有f(n-2)種跳法。所以,f(n)=f(n-1)+f(n-2)
如果先后次序不同算相同的結果的話:n/2 +1種結果
變種2:變態跳臺階
一只青蛙一次可以跳上1級臺階,也可以跳上2級……它也可以跳上n級。求該青蛙跳上一個n級的臺階總共有多少種跳法。
思路1:
如果只有一級臺階,有1種跳法;如果有2級,有2種跳法。(初始)
如果有n級臺階,那么第一步可以跳1,2,3,……,n。所以剩下的分別有f(n-1),f(n-2),f(n-3)……,f(1),1種跳法。
用一個長度為n的數組存儲之前計算出來的值。
f(n)=f(n-1)+f(n-2)+……+f(1)+1

public class Solution {
    public int JumpFloorII(int target) {
        if(target==0)return 0;
        if(target==1)return 1;
        if(target==2)return 2;
        int[] pre = new int[target];
        pre[0]=1;
        pre[1]=2;
        int result=pre[0]+pre[1];
        for(int i=2;i<target;i++){
            pre[i]=result+1;
            result+=pre[i];
        }
        return pre[target-1];
    }
}

然而,這種方式仍然不是最簡單的
由于
f(n)=f(n-1)+f(n-2)+……+f(1)+1
f(n-1)=f(n-2)+……+f(1)+1
所以f(n) = 2f(n-1)=22f(n-2)=22……f(2) = 2(n-2)*f(2)=2(n-1)
可以根據這個遞推式去做。
pow(2,n-1)或者1<<n-1

變種3:矩形覆蓋
題目描述
我們可以用21的小矩形橫著或者豎著去覆蓋更大的矩形。請問用n個21的小矩形無重疊地覆蓋一個2*n的大矩形,總共有多少種方法?
可以轉化成變種1的思考方式

public class Solution {
    public int RectCover(int target) {
        if(target==0)return 0;
        if(target==1)return 1;
        if(target==2)return 2;
        int pre1= 1;
        int pre2= 2;
        int result =0;
        for(int i=2;i<target;i++){
            result=pre1+pre2;
            pre1=pre2;
            pre2 = result;
        }
        return result;
    }
}

8.二進制中1的個數

位運算是屬于二進制數的運算
基本知識

  1. 代碼中在數字前加前綴
    0x十六進制
    0b二進制
    0八進制
  2. 操作
    &
    ^
    |
    m<<n:最左邊的n位將被丟棄,同時在右邊補上n個0
    m=m>>n:最右邊的n位將被丟棄。如果是有符號數,用數字的符號位填補
    左邊的n位
    m>>>n:無符號右移,補0
  3. 原碼、反碼和補碼
    https://blog.csdn.net/hittata/article/details/9108323
    計算機中的負數表示是補碼
    以負數-5為例:
    1.先將-5的絕對值轉換成二進制,即為0000 0101;
    2.然后求該二進制的反碼,即為 1111 1010;
    3.最后將反碼加1,即為:1111 1011
    所以Java中Integer.toBinaryString(-5)結果為11111111111111111111111111111011. Integer是32位(bit)的.

題目描述
輸入一個整數,輸出該數二進制表示中1的個數。其中負數用補碼表示。
思路1:整數右移,直到為0(負數會陷入死循環)。但是java提供無符號右移方式,此時是可行的

public class Solution {
    public int NumberOf1(int n) {
        int count=0;
        while(n!=0){
            if((n&1)!=0){
              count++;  
            }
            n=n>>>1;
        }
        return count;
    }
}

思路2:設置一個無符號整型的flag,然后每次flag左移,n&flag!=0時count數+1,直到flag為0(java語言中沒有無符號整型這樣的變量)

public class Solution {
    public int NumberOf1(int n) {
        int count=0;
        int flag=1;
        while(flag!=0){
            if((n&flag)!=0){
              count++;  
            }
            flag = flag<<1;
        }
        return count;
    }
}

思路3:n&(n-1)會將n的最右邊一位1變成0。令n=n&(n-1),直到n為0

public class Solution {
    public int NumberOf1(int n) {
        int count=0;
        while(n!=0){
            n=n&(n-1);
            count++;
        }
        return count;
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372