劍指offer4-解決面試題的思路

4.2 畫圖java

題目-二叉樹的鏡像node

操做給定的二叉樹,將其變換爲源二叉樹的鏡像。 

思路數組

 

求二叉樹鏡像的過程:1)交換根結點的左右子樹;2)交換值爲10的結點的左右子結點;3)交換值爲6的結點的左右子結點。數據結構

總結這個過程就是:前序遍歷這棵樹的每一個結點,若是遍歷到的結點有子結點,就交換它的兩個子結點。直到交換完全部非葉子結點的左右子結點。dom

解答函數

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
 
    public TreeNode(int val) {
        this.val = val;
 
    }
 
}
*/
public class Solution {
    public void Mirror(TreeNode root) {
        if((root==null) || (root.left==null && root.right==null))
            return;
        //交換根結點的左右子樹
        TreeNode temp=root.left;
        root.left=root.right;
        root.right=temp;
        //交換原來根結點的左子結點的左右子結點
        if(root.left!=null)
            Mirror(root.left);
        //交換原來根結點的右子結點的左右子結點
        if(root.right!=null)
            Mirror(root.right);
    }
}

 

題目20-順序打印矩陣this

輸入一個矩陣,按照從外向裏以順時針的順序依次打印出每個數字,例如,若是輸入以下4 X 4矩陣: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 則依次打印出數字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

spa

  

以從外圈到內圈的順序依次打印,把矩陣想象成若干個圈。設置一個循環,每一次打印矩陣中的一個圈。3d

須要考慮到若是矩陣不是N*N的,那麼打印的最後一圈可能退化成只有一行、只有一列或者只有一個數字,所以打印這樣的一圈就不須要4步。指針

分析打印時每一步的前提條件:

1)第一步無條件

2)第二步無條件

3)第三步時圈內至少有兩行兩列,即要求終止行號>起始行號外 && 還要求終止列號>起始列號。(也就是打印時當終止行號!=起始行號)

4)第四步時圈內至少有三行兩列,即要求終止行號-起始行號>2 && 終止列號>起始列號。(也就是終止列號!=起始列號)

解答

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
       if(matrix == null)
           return null;
        ArrayList<Integer> ret = new ArrayList<>();
        int r1=0, r2=matrix.length-1, c1=0, c2=matrix[0].length-1;
        while(r1<=r2 && c1<=c2){
       //從左往右打印一行
for(int i=c1; i<=c2; i++){ ret.add(matrix[r1][i]); }
  //從上往下打印一列
for(int i=r1+1;i<=r2;i++){ ret.add(matrix[i][c2]); }
  //從右往左打印一行
if(r1!=r2){ for(int i=c2-1;i>=c1;i--){ ret.add(matrix[r2][i]); } }
//從下往上打印一列
if(c1!=c2){ for(int i=r2-1;i>r1;i--){ ret.add(matrix[i][c1]); } } r1++; r2--; c2--; c1++; } return ret; } }

 

4.3 舉例

題目-包含min函數的棧

定義棧的數據結構,請在該類型中實現一個可以獲得棧中所含最小元素的min函數(時間複雜度應爲O(1))。 

思路

1.首先的想法是每次壓入一個新元素進棧,將棧裏的全部元素排序,讓最小的元素位於棧頂,這樣能夠在O(1)時間獲得最小元素。但這樣不能保證最後壓入棧的元素能先出棧,所以這是數據結構就是否是棧了。

2.第二種思路是在棧裏添加一個存放最小元素的成員變量,每次壓入一個新元素入棧,更新最小元素變量。但這樣若是當前最小元素被彈出棧,就不能獲得下一個最小元素,所以始終是存儲了一個最小元素。

3.也就是咱們不只須要保存最小元素,還但願可以保存次小元素。那咱們考慮構建一個輔助棧(把每次的最小元素保存在輔助棧),過程以下:

 

即每次都把最小元素壓入輔助棧,從而保證輔助棧的棧頂一直都是最小元素。當最小元素從數據棧內被彈出後,輔助棧的棧頂元素也被彈出,此時輔助棧的新棧頂元素就是下一個最小值。

棧的基本操做:

進棧:stack.push(Object);//返回入棧的內容

      stack.add(Object);//返回true/false

出棧:stack.pop();//輸出並刪除棧頂元素

   stack.peek();//輸出且不刪除棧頂元素 

解答

import java.util.Stack;
 
public class Solution {
 
    //數據棧
    private Stack<Integer> dataStack = new Stack<>();
    //輔助棧
    private Stack<Integer> minStack = new Stack<>();
     
    public void push(int node) {
        dataStack.push(node);
//將最小元素壓入輔助棧
if(minStack.size()==0 || node<minStack.peek()) minStack.push(node); else minStack.push(minStack.peek()); } public void pop() { minStack.pop(); dataStack.pop(); } public int top() { return dataStack.peek(); } public int min() { return minStack.peek(); } }

 

題目-棧的壓入、彈出序列

輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否可能爲該棧的彈出順序。假設壓入棧的全部數字均不相等。例如序列1,2,3,4,5是某棧的壓入順序,序列4,5,3,2,1是該壓棧序列對應的一個彈出序列,但4,3,5,1,2就不多是該壓棧序列的彈出序列。(注意:這兩個序列的長度是相等的) 

思路

創建一個輔助棧,把輸入的第一個序列中的數字依次壓入該輔助棧,並按照第二個序列的順序依次從該棧中彈出數字(當該數字爲棧頂數字時,能夠順利彈出)。

以彈出序列爲四、五、三、二、1爲例進行分析。壓入順序爲一、二、三、四、5

首先第一個須要被彈出的數字是4,所以4須要先被壓入到輔助棧中。那麼按照壓入順序,要把4壓入棧,一、二、3都須要被壓入棧。此時棧中爲一、二、三、4;

接着把4彈出棧,棧中剩下一、二、3;

接着須要被彈出的數字是5,那麼5須要被壓入棧,即將壓入序列中4之後的數字壓入輔助棧,直到5入棧。此時棧中爲一、二、三、5;

接着把5彈出棧,棧中剩下一、二、3;

以後每彈出的數字都在棧頂,能夠被順利彈出。

 接下來分析彈出序列爲四、三、五、一、2,壓入序列爲一、二、三、四、5.

出現當須要彈出一個數字時,該數字並不在棧頂,而且不在壓入序列中的待入棧部分。說明不符合

經過舉例分析,總結出判斷一個序列是否爲棧的彈出序列的規律:

1.若是下一個彈出數字爲棧頂數字,則直接彈出;

2.若是下一個彈出數字不爲棧頂數字,則將壓棧序列中還未入棧的數字入棧,直到下一個彈出數字爲棧頂數字;

3.若是全部數字都入棧仍未在棧頂找到下一個彈出數字,則該序列不可能爲彈出序列;

4.若是全部數字都被順利彈出,則爲彈出序列。 

解答

import java.util.ArrayList;
import java.util.Stack;
 
public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        int n=pushA.length;
        Stack<Integer> stack = new Stack<>();
        for(int pushIndex=0,popIndex=0;pushIndex<n;pushIndex++){
            //將壓棧序列中還沒入棧的數字壓入輔助棧,直到把下一個須要彈出的數字壓入棧頂爲止
            stack.push(pushA[pushIndex]);
            //每壓入一次數字,都判斷一次當前數字是否爲須要彈出的數字。
            //若是下一個彈出的數字在棧頂,直接彈出
            while(popIndex<n && !stack.isEmpty() && stack.peek()==popA[popIndex]){
                stack.pop();
                popIndex++;
            }
        }
        //根據處理完的序列是否爲空,判斷序列是否爲彈出序列
        return stack.isEmpty();
    }
}

 

題目-從上往下打印二叉樹

從上往下打印出二叉樹的每一個節點,同層節點從左至右打印。 

 打印順序爲:八、六、十、五、七、九、11

思路

按層打印的順序,那麼就應該先打印根結點,而後再打印根結點的子結點,再分別打印根結點的左右子節點的子結點。

那麼咱們打印一個結點時,就把該結點的左右子結點加入到數據容器中。

打印順序就是先入先出,那麼構成的數據容器爲一個隊列。

總結出打印的規律:

1.每次打印一個結點,若是該結點有子結點,就將該結點的子結點放到隊列的末尾;

2.到隊列的頭部取出最先進入隊列的結點,重複以上的打印操做;

3.直到隊列中全部的結點都被打印出來爲止。

隊列的基本操做:

remove();//移除並返回隊列頭部的元素

poll();//移除並返回隊列頭部的元素,在用空集合調用時不會拋出異常,而是返回null。更適合容易出現異常條件的狀況

offer();//添加一個元素並返回true。一些隊列有大小限制,所以若是想在一個滿的隊列中加入一個新項,多出的項就會被拒絕。返回添加結果

add();//增長一個元素

peek();//返回隊列頭部的元素&&更適合容易出現異常條件的狀況。隊列爲空時,返回null

element();//返回隊列頭部的元素&&隊列爲空時,拋出異常

解答

import java.util.ArrayList;
import java.util.Queue;
import java.util.LinkedList;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
 
    public TreeNode(int val) {
        this.val = val;
 
    }
 
}
*/
public class Solution {
    public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
        ArrayList<Integer> list = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        //直到隊列中全部的結點都被打印
        while(!queue.isEmpty()){
            int size=queue.size();
            while(size-->0){
                //返回第一個元素,並刪除
                TreeNode t = queue.poll();
                if(t==null)
                    continue;
                list.add(t.val);
                queue.add(t.left);
                queue.add(t.right);
            }
        }
        return list;
    }
}

 

題目-二叉搜索樹的後序遍歷序列

輸入一個整數數組,判斷該數組是否是某二叉搜索樹的後序遍歷的結果。若是是則輸出Yes,不然輸出No。假設輸入的數組的任意兩個數字都互不相同。 

思路

 

這個是後序遍歷序列五、七、六、九、十一、十、8對應的二叉搜索樹。(左右根)

能夠看出:後序遍歷的序列中,最後一個數字是樹的根結點的值。數組中前面的數字能夠分爲兩部分:第一部分是左子樹結點的值,它們比根結點都小;第二部分是右子樹結點的值,比根結點都大。

而後可使用一樣的判斷方式肯定數組左右子樹對應的子樹方式,是一個遞歸的過程。

因此發現規律就是:

  1)找到根結點;

  2)根據根結點,找到左子樹結點和右子樹的分割點;

  3)再判斷如今獲得的右子樹是否都大於根結點,若是存在小於根結點的結點,則不符合規則。

解答

public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        //後序遍歷是左右根
        if(sequence == null || sequence.length == 0)
            return false;
        return verify(sequence, 0, sequence.length-1);
    }
     
    private boolean verify(int [] sequence, int first, int last){
        //
        if(last-first <= 1)
            return true;
        //根結點
        int rootVal = sequence[last];
        int curIndex = first;
        //找到左子樹的最右結點,根結點大於左子樹的結點
        while(curIndex < last && sequence[curIndex] <= rootVal)
            curIndex++;
        //若是右子樹的結點 存在小於根節點的,則不符合
        for(int i=curIndex;i<last;i++){
            if(sequence[i]<rootVal)
                return false;
        }
        //查找左子樹和右子樹是否符合後序遍歷
        return verify(sequence, first, curIndex-1) && verify(sequence, curIndex, last-1);
    }
     
}

 

題目-二叉樹中和爲某一值的路徑

輸入一顆二叉樹的跟節點和一個整數,打印出二叉樹中結點值的和爲輸入整數的全部路徑。路徑定義爲從樹的根結點開始往下一直到葉結點所通過的結點造成一條路徑。(注意: 在返回值的list中,數組長度大的數組靠前) 

若是輸入該二叉樹與22,那麼輸出兩條路徑:十、五、7;十、12.

路徑是從根結點出發到葉結點,也就是路徑老是以根結點爲起始點。

所以咱們能夠獲得規律:

1)當用前序遍歷的方式訪問某一結點時,就把該結點添加到路徑上,並累加該結點的值;

2)若是累加的和 正好爲 輸入的整數,且剛添加的結點爲葉結點時,則當前的路徑符合要求,打印出來;

3)若是剛添加的不是葉結點,那麼就繼續訪問它的子結點;(符合要求就打印)

4)當前結點結束訪問時,就自動回到它的父結點。並在路徑上刪除這個結點,而且減去當前結點的值。使得如今的路徑爲根結點到父結點,由於這條路徑走完,能夠走父結點的另外子結點的路徑了。

5)依次遞歸。

解答

import java.util.ArrayList;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
 
    public TreeNode(int val) {
        this.val = val;
 
    }
 
}
*/
public class Solution {
    private ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        backtracking(root, target, new ArrayList<>());
        return ret;
    }
     
    private void backtracking(TreeNode node, int target, ArrayList<Integer> path){
        if(node==null)
            return;
        path.add(node.val);
        target=target-node.val;
        //在當前根結點就已經找到了一條路徑。符合1.結點爲整數之和;2.葉子結點
        if(target==0 && node.left==null && node.right==null)
            ret.add(new ArrayList<>(path));
        //若是不是葉子結點,就遍歷子節點
        else{
            //沿着左子樹繼續查找,還未找到路徑
            backtracking(node.left, target, path);
            //沿着右子樹查找
            backtracking(node.right, target, path);
        }
        //當遍歷到了葉子結點,即當前結點訪問結束後,遞歸函數將自動回到它的父結點
        //在返回到父結點以前,在路徑上刪除當前結點,因爲這一條路徑不符合條件,返回當前結點的上一級。以確保返回父結點時路徑恰好是從根結點到父結點的路徑
        //即刪除下標爲path.size()-1的元素,就是剛添加的;若是是remove(Integer.valueOf(n)),就是刪除值爲n的元素
        path.remove(path.size()-1);
    }
}

 

4.4 分解

題目-複雜鏈表的複製

輸入一個複雜鏈表(每一個節點中有節點值,以及兩個指針,一個指向下一個節點,另外一個特殊指針指向任意一個節點),返回結果爲複製後複雜鏈表的head。(注意,輸出結果中請不要返回參數中的節點引用,不然判題程序會直接返回空) 

 實線箭頭表示next指針,虛線箭頭表示random指針 

思路

1)根據原始鏈表的每一個結點N建立對應的N‘。把N’連接在N的後面; 

2)設置複製出來的結點N‘的指向。若原來的結點N的random是指向結點S,那麼複製出來的結點N’的random是指向S‘(S’爲S複製出來的下一結點)


3)把這個長鏈表進行拆分:奇數位置上的結點組成原始鏈表,偶數位置上的結點組成複製出來的鏈表。

 

另外一種時間空間複雜度更高一些的作法是:建立一個哈希表,用來存儲<N,N'>的配對信息。若是原來鏈表的N指向S,那麼N'就是指向S'。能夠經過哈希表在O(1)時間根據S找到S',但這樣須要一個大小爲O(n)的哈希表,用空間換時間。

解答

/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;
 
    RandomListNode(int label) {
        this.label = label;
    }
}
*/
public class Solution {
    public RandomListNode Clone(RandomListNode pHead)
    {
        if(pHead == null)
            return null;
        //在每一個節點後面插入複製的節點
        RandomListNode cur = pHead;
        while(cur != null){
            //建立新節點
            RandomListNode clone = new RandomListNode(cur.label);
            //把新節點連接到原節點的後面
            clone.next = cur.next;
            cur.next = clone;
            cur = clone.next;
        }
        //對複製節點的random連接進行賦值
        cur = pHead;
        while(cur != null){
            //原始鏈表上的節點N的random指向S,那麼其對應複製出來的N'是N的next指向的節點
            RandomListNode clone = cur.next;
            if(cur.random != null)
                //S'也是S的next指向的節點
                clone.random=cur.random.next;
            cur = clone.next;
        }
        //拆分
        cur = pHead;
        RandomListNode pCloneHead = pHead.next;
        while(cur.next != null){
            //隔一位取
            RandomListNode next = cur.next;
            cur.next = next.next;
            cur = next;
        }
        return pCloneHead;
    }
}

 

題目-二叉搜索樹與雙向鏈表

輸入一棵二叉搜索樹,將該二叉搜索樹轉換成一個排序的雙向鏈表。要求不能建立任何新的結點,只能調整樹中結點指針的指向。 

思路

二叉樹中,每一個結點都有兩個指向子結點的指針。在雙向鏈表中,每一個結點也有兩個指針,分別指向前一個結點和後一個結點。能夠發現二叉搜索樹與雙向鏈表的類似性。轉換時能夠將原來指向右子結點的指針調整爲鏈表中指向後一個結點指針。

而且轉換後的鏈表是有序的,那麼咱們可使用中序遍從來實現,中序遍歷是左根右,即按從小到大的順序遍歷二叉樹的每一個結點。

按照中序遍歷的順序,咱們遍歷轉換到根結點時,它的左子樹已經轉換爲一個排序的鏈表了,而且處在鏈表中的最後一個結點時當前值最大的結點。

咱們把值爲8的結點和根結點連接起來,此時鏈表中的最後一個結點就是10.

接着再轉換右子樹,將根結點與右子樹中最小的結點連接起來。

 那麼左右子樹的轉換,依然使用遞歸完成。

解答

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
 
    public TreeNode(int val) {
        this.val = val;
 
    }
 
}
*/
public class Solution {
  //定義鏈表當前結點
private TreeNode pre=null;
  //定義鏈表頭部的結點
private TreeNode head=null; public TreeNode Convert1(TreeNode pRootOfTree) { inOrder(pRootOfTree); return head; } private void inOrder(TreeNode node){ if(node==null) return; inOrder(node.left); //通過上一步,此時左子樹已經轉換爲一個排序的鏈表了,並且鏈表的最後一個結點是當前值最大的結點 //接下來把左子樹最大結點(當前鏈表最後一個結點)與根結點連接 node.left=pre; if(pre!=null) pre.right=node; pre=node; if(head==null) head=node;
        //遍歷轉換右子樹&將根結點與右子樹最小的結點連接 inOrder(node.right); }


    public TreeNode Convert2(TreeNode pRootOfTree) {
        if(pRootOfTree==null)
            return null;
        if(pRootOfTree.left==null && pRootOfTree.right==null)
            return pRootOfTree;
        //轉換左子樹爲雙鏈表,返回鏈表頭結點
        TreeNode left=Convert(pRootOfTree.left);
        TreeNode p=left;
        //定位到左子樹雙鏈表的最後一個結點
        while(p!=null && p.right!=null){
            p=p.right;
        }
        //若是左子樹不爲空,將當前結點加到左子樹鏈表
        if(left!=null){
            p.right=pRootOfTree;
            pRootOfTree.left=p;
        }
        //轉換右子樹,並返回鏈表頭結點
        TreeNode right=Convert(pRootOfTree.right);
        //若是右子樹不爲空,將當前結點加到右子樹鏈表
        if(right!=null){
            right.left=pRootOfTree;
            pRootOfTree.right=right;
        }
        //根據左子樹鏈表是否爲空,肯定返回結點
        return left!=null?left:pRootOfTree;
    } }

 

題目-字符串的排列

輸入一個字符串,按字典序打印出該字符串中字符的全部排列。例如輸入字符串abc,則打印出由字符a,b,c所能排列出來的全部字符串abc,acb,bac,bca,cab和cba。

思路

 

 以「abc」爲例,

1)將a固定住,求bc的全排列,獲得abc,acb;

2)將a與b交換,bac。再求ac的全排列,獲得bac,bca;

3)將a與b交換回來,再將a與c交換,cba。再求ba的全排列,獲得cba,cab;

能夠遞歸獲得。

那麼咱們獲得規律就是:若以i爲開頭的全排序

以當前i開始,依次與後面的元素交換,而後求i+1爲開頭的全排列,最後還要交換回來。

終止條件是當遍歷到ch.length-1,只有一個元素,那麼就遍歷完了

解答

import java.util.ArrayList;
import java.util.Collections;
 
public class Solution {
    public ArrayList<String> Permutation(String str) {
        ArrayList<String> res=new ArrayList<>();
        if(str==null || str.length()==0){
            return res;
        }
        char[] ch = str.toCharArray();
        Core(res, ch, 0);
        //字典序排序
        Collections.sort(res);
        return res;
    }
     
    public void Core(ArrayList<String> res, char[] ch, int i){
        //若是這一輪交換完,且當前還沒出現這種排序的ch,則添加(由於序列中可能有重複數字,因此保證不重複)
        //遍歷到ch.length時,只有一個元素,遞歸終止
        if(i==ch.length && !res.contains(String.valueOf(ch))){
            res.add(String.valueOf(ch));
            return;
        }
        //嘗試ch[i]全部可能的選擇
        //遍歷字符串數組,以當前i爲首,依次與後面的元素交換,而後求i+1爲開頭的全排序
        for(int k=i; k<ch.length; k++){
            swap(ch,i,k);
            Core(res, ch, i+1);
            //至關於復原了當前遞歸函數中的ch,在下一輪循環中考慮該index位置上的其餘可能的選項
            swap(ch,i,k);
        }
         
    }
     
    public void swap(char[] ch, int i, int j){
        char temp=ch[i];
        ch[i]=ch[j];
        ch[j]=temp;
    }
}
相關文章
相關標籤/搜索