算法 | 一周刷完《劍指Offer》 Day2:第17~26題

寫在前面

上一篇:算法 | 一周刷完《劍指Offer》 Day1:第1~16題
下一篇:算法 | 一周刷完《劍指Offer》 Day3:第27~37題


Day2:第17~26題

今天的題涉及遞歸及二叉樹較多,需要深入理解其邏輯和原理。

  • T17. 樹的子結構
  • T18. 二叉樹的鏡像
  • T19. 順時針打印矩陣
  • T20. 包含 min 函數的棧
  • T21. 棧的壓入、彈出序列
  • T22. 從上往下打印二叉樹
  • T23. 二叉搜索樹的后序遍歷序列
  • T24. 二叉樹中和為某一值的路徑
  • T25. 復雜鏈表的復制
  • T26. 二叉搜索樹與雙向鏈表

T17. 樹的子結構

題目描述

輸入兩棵二叉樹A,B,判斷B是不是A的子結構。(ps:我們約定空樹不是任意一個樹的子結構)

解題思路

設置終止條件進行判斷,將B樹與A樹,A樹左子樹,A樹右子樹進行比較,遞歸進行即可。

    public boolean HasSubtree(TreeNode root1, TreeNode root2) {
        if(root1 == null || root2 == null) return false;
        return isSubtree(root1, root2) //root2與root1比較
                || HasSubtree(root1.left, root2) //root2與root1左子樹比較,遞歸邏輯
                || HasSubtree(root1.right, root2); //root2與root1右比較,遞歸邏輯
        //以上三種情況任一為true,即證明root2是root1的子結構
    }
    
    private boolean isSubtree(TreeNode root1, TreeNode root2) {
        //終止判定
        if(root1 == null && root2 == null) return true;//為null,能執行到此步且相同,為子結構
        if(root1 == null) return false;//root1為null,root2不為null,不同,不為子結構
        if(root2 == null) return true;//root1不為null,root2為null,能執行到此步說明相同,為子結構
        if(root1.val != root2.val) return false;//root1,root2都不為null,val不同,不為子結構
        
        //能執行到此步,說明未判定完,繼續對root1,root2的左右子樹分別遞歸此方法進行判斷,均為true則為子結構
        return isSubtree(root1.left, root2.left) && isSubtree(root1.right, root2.right);
    }

T18. 二叉樹的鏡像

題目描述

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

解題思路

交換每個結點的左右子樹,并對該結點的左右子結點分別進行此操作,遞歸進行即可。

    public void Mirror(TreeNode root) {
        if(root == null) return;
        
        //交換左右子樹
        swap(root);
        //分別對root左右子樹進行交換,遞歸調用此方法即可
        Mirror(root.left);
        Mirror(root.right);
    }
    
    private void swap(TreeNode root) {
        TreeNode tmp = root.left;
        root.left = root.right;
        root.right = tmp;
    }

T19. 順時針打印矩陣

題目描述

輸入一個矩陣,按照從外向里以順時針的順序依次打印出每一個數字,例如,如果輸入如下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.

解題思路

順時針繞圈打印即可,從外向里每次循環繞一圈。

注意:一定細心,出錯基本都是馬虎造成的。

    public ArrayList<Integer> printMatrix(int[][] matrix) {
        ArrayList<Integer> list = new ArrayList<>();
        
        if(matrix == null || matrix[0] == null) return list;
        
        int rowTop = 0, rowBottom = matrix.length - 1;
        int colLeft = 0, colRight = matrix[0].length - 1;
        
        while(colLeft <= colRight && rowTop <= rowBottom) {//跳出條件
            for(int i = colLeft; i <= colRight; i ++) {//添加上邊一行,從左到右
                list.add(matrix[rowTop][i]);
            }
            for(int i = rowTop + 1; i <= rowBottom; i ++) {//添加右邊一列,從上到下,注意去掉已添加的【右上角】。
                list.add(matrix[i][colRight]);
            }
            if(rowTop != rowBottom) {//若相等則已到同一行,無可繼續添加的
                for(int i = colRight - 1; i >= colLeft; i --) {//添加下邊一行,從右到左,注意去掉已添加的【右下角】。
                    list.add(matrix[rowBottom][i]);
                }
            }
            if(colLeft != colRight) {//若相等則已到同一列,無可繼續添加的
                for(int i = rowBottom - 1; i > rowTop; i --) {//添加左邊一列,從下到上,注意去掉已添加的【左下角】及【左上角】。
                    list.add(matrix[i][colLeft]);
                }
            }
            
            colLeft ++;
            colRight --;
            rowTop ++;
            rowBottom --;
        }
        
        return list;
    }

T20. 包含 min 函數的棧

題目描述

定義棧的數據結構,請在該類型中實現一個能夠得到棧中所含最小元素的min函數(時間復雜度應為O(1))。

解題思路

定義兩個棧,stack存入棧的數,minStack存該數入棧后棧內的最小數min。

注意:兩個棧大小是相同的,同步入棧及出棧。即哪怕入棧的數不是最小的,也把那個最小的再入一次minStack。

    private Stack<Integer> stack = new Stack<>();
    private int min = Integer.MAX_VALUE;
    //minStack用于存儲任一元素入棧時,當前棧內的最小值,與stack是同步入棧出棧的,即兩個棧內元素數目相同
    private Stack<Integer> minStack = new Stack<>();
    
    public void push(int node) {
        stack.push(node);
        if(min > node) {
            min = node;
        }
        minStack.push(min);
    }
    
    public void pop() {
        stack.pop();
        minStack.pop();
        min = minStack.peek();
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int min() {
        return min;
    }

T21. 棧的壓入、彈出序列

題目描述

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

解題思路

建個棧,模擬出入棧順序即可。

注意:每次入棧后,要對出棧順序的數組進行檢測,循環把該出棧的出了,再進行下次入棧。

    public boolean IsPopOrder(int[] pushA, int[] popA) {//pushA和popA長度相同
        //建個棧,模擬入棧出棧操作即可
        Stack<Integer> stack = new Stack<>();
        int popIndex = 0;
        
        for(int pushIndex = 0; pushIndex < pushA.length; pushIndex ++) {
            stack.push(pushA[pushIndex]);//按pushA順序入棧
              
            while(popIndex < popA.length && popA[popIndex] == stack.peek()) {//相同說明可出棧,即模擬popA順序進行出棧操作
                stack.pop();
                popIndex ++;
            }
        }

        //若棧空,說明pushA入棧能按popA順序出棧
        return stack.isEmpty();
    }

T22. 從上往下打印二叉樹

題目描述

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

解題思路

二叉樹層輸出,使用廣度優先搜索(BFS)即可。

使用隊列實現,邊出隊邊輸出,同時將其左右子結點壓入隊列。

    public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
        ArrayList<Integer> list = new ArrayList<>();
        
        if(root == null) return list;
        
        //使用隊列實現,不斷按層壓入及輸出
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        
        while(!queue.isEmpty()) {
            TreeNode tmp = queue.poll();
            //先左后右按順序壓入子結點
            if(tmp.left != null) queue.add(tmp.left);
            if(tmp.right != null) queue.add(tmp.right);
            
            list.add(tmp.val);
        }
        
        return list;
    }

T23. 二叉搜索樹的后序遍歷序列

題目描述

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

解題思路

二叉搜索樹: 若它的左子樹不空,則左子樹上所有結點的值均小于它的根結點的值;若它的右子樹不空,則右子樹上所有結點的值均大于它的根結點的值;它的左、右子樹也分別為二叉搜索樹(二叉排序樹)。

后序遍歷:左 -> 右 -> 根。

因此,后序遍歷最后一個數為根結點。通過根結點可把后序遍歷分為兩部分前半部分為小于根結點的左子樹后半部分為大于根結點的右子樹。然后根據此原理,遞歸對左右子樹分別用此方法進行驗證即可。

    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(first >= last) return true;
        
        int rootValue = sequence[last];//后序遍歷的根結點為最后一個
        int index = first;
        
        while(sequence[index] <= rootValue && index < last) {//比根結點小的為左子樹,大的為右子樹
            index ++;
        }
        //此時sequence[index]是第一個比根結點大的值
        //可將sequence[0]~sequence[index-1]認為是左子樹,sequence[index]~sequence[last-1]認為是右子樹
        for(int i = index; i < last; i ++) {
            if(sequence[i] < rootValue) {//若右子樹中存在比根結點小的,則不是二叉搜索樹
                return false;
            }
        }
        
        //此時分別對根結點的左右子樹進行迭代判斷,全部為true則是后序遍歷
        return verify(sequence, first, index - 1)
                && verify(sequence, index, last - 1);//last為根結點
    }

T24. 二叉樹中和為某一值的路徑

題目描述

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

解題思路

深度優先搜索(DFS),通過減法的思想不斷用target減去當前結點值,能減為0則是一條路徑。

注意:路徑要求最后到達葉子結點
迭代過程中需把當前值在path中移除以保證路徑正確,相當于回退到上一步的路徑。(詳見代碼)

    private ArrayList<ArrayList<Integer>> result = new ArrayList<>();
    
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
        if(root == null) return result;
        
        ArrayList<Integer> path = new ArrayList<>();
        findPathDFS(root, target, path);
        
        return result;
    }
    
    private void findPathDFS(TreeNode node, int target, ArrayList<Integer> path) {
        if(node == null) return;
        
        path.add(node.val);
        target -= node.val;//減法的思想,目標值能減為0則是一條路徑
        if(target == 0 && node.left == null && node.right == null) {//已經到達葉子結點且targe正好減完
            result.add(new ArrayList<Integer>(path));
        } else if(target > 0) {//若>0則繼續對其左右子結點進行迭代判斷
            findPathDFS(node.left, target, path);
            findPathDFS(node.right, target, path);
        }
        
        path.remove(path.size() - 1);//此步重要,迭代過程中需把當前值在path中移除以保證路徑正確,相當于回退到上一步的路徑
    }

T25. 復雜鏈表的復制

題目描述

輸入一個復雜鏈表(每個結點中有結點值,以及兩個指針,一個指向下一個結點,另一個特殊指針指向任意一個結點),返回結果為復制后復雜鏈表的head。(注意,輸出結果中請不要返回參數中的結點引用,否則判題程序會直接返回空)

解題思路

step1:在每個結點的后面(或者說每個結點與下一個結點中間)插入新結點。該新結點為克隆結點,這么做是為了連接random結點。

step2:連接random結點。

step3:拆分鏈表,下邊為原鏈表,上邊為clone鏈表。

    public RandomListNode Clone(RandomListNode pHead) {
        if(pHead == null) return null;
        
        //step1:在每個結點的后面(或者說每個結點與下一個結點中間)插入【新結點】
        //該新結點為克隆結點,這么做是為了連接random結點
        RandomListNode tmp = pHead;
        while(tmp != null) {
            RandomListNode cloneNode = new RandomListNode(tmp.label);
            
            //插入clone結點
            cloneNode.next = tmp.next;
            tmp.next = cloneNode;
            //移到原鏈表的下一個結點
            tmp = cloneNode.next;
        }
        
        //step2:連接random結點
        tmp = pHead;
        while(tmp != null) {
            RandomListNode cloneNode = tmp.next;
            if(tmp.random != null) {
                cloneNode.random = tmp.random.next;//tmp.random是原鏈表的結點,tmp.random.next才是那個結點的clone結點
            }
            tmp = cloneNode.next;
        }
        
        //step3:拆分鏈表(詳見圖片)
        tmp = pHead;
        RandomListNode cloneHead = tmp.next;
        while(tmp.next != null) {
            RandomListNode node = tmp.next;
            tmp.next = node.next;
            tmp = node;
        }
        
        return cloneHead;
    }

T26. 二叉搜索樹與雙向鏈表

題目描述

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

解題思路

由于二叉搜索樹左子結點 < 根結點 < 右子結點的性質,題目實質上是二叉搜索樹中序遍歷,改結點的指針。left代表雙向鏈表的prev指針,right代表next指針。

    private TreeNode pre = null;//用于記錄上一個結點
    private TreeNode head = null;
    
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree == null) return null;
        
        inOrder(pRootOfTree);
        
        return head;
    }
    
    private void inOrder(TreeNode node) {
        if(node == null) return;
        
        //實質上是中序遍歷,改結點的指針。left代表雙向鏈表的prev指針,right代表next
        //左
        inOrder(node.left);
        
        //根
        //改指針的指向(只需與上一個結點相連即可)
        node.left = pre;//連上一個
        if(pre != null) {//如果上一個不為null,連此時這個
            pre.right = node;
        }
        pre = node;//將pre移向此時這個結點,為下一次迭代做準備
        
        if(head == null) head = node;//只在第一次找到最小結點時作為頭結點
        
        //右
        inOrder(node.right);
    }

項目地址https://github.com/JohnnyJYWu/offer-Java

上一篇算法 | 一周刷完《劍指Offer》 Day1:第1~16題
下一篇算法 | 一周刷完《劍指Offer》 Day3:第27~37題

希望這篇文章對你有幫助~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容