不怕面試被問了!二叉樹算法大盤點

2021-02-21 國科學院

本文經授權轉自公眾號CSDN(ID:CSDNnews)

樹結構對於程式設計師來說應該不陌生,特別是二叉樹,基本只要接觸算法這一類的都一定會碰到的,所以我打算通過一篇文章,對二叉樹結構的相關算法進行總結匯總,思路和代碼實現相結合,讓你不在懼怕二叉樹。(ps:後面我還想寫一篇樹結構的高級篇,就是多叉數,就是對我平時看算法論文碰到的一些新奇的算法,比如B樹、B+樹,還有我一種叫做Bed樹的新奇算法等等)

下面的思路講解中,我會給出一個類偽代碼的思路,然後進行相關說明,也就是一種思路框架,有了思路框架,以後碰到問題就直接交給框架完成。本文主要說一下二叉搜索樹(Binary Search Tree,簡稱 BST),BST是一種很常用的的二叉樹。它的定義是:一個二叉樹中,任意節點的值要大於等於左子樹所有節點的值,且要小於等於右邊子樹的所有節點的值。如下就是一個符合定義的 BST:

後面如果遇到特殊的思路結構,如多叉樹,我會特別說明。首先我們先給出二叉樹的節點定義(這個定義應該不陌生吧,有刷算法題都會碰到)。

public class TreeNode {

      int val;

      TreeNode left;

      TreeNode right;

      TreeNode(int x) { val = x; }

}

1、遞歸


不過這裡要說明一點的是,在偽代碼中的「進行想要的操作」的位置,不一定就在我放置的位置,具體位置還需要我們根據不同的實際需求進行判斷。不過因為前中後序的遍歷,遞歸進入的時機應該需要和我的一樣。

先序遍歷

遍歷根節點,如果根節點為空,返回;否則,遍歷根節點,然後先序遍歷左子樹,再先序遍歷右子樹。

public void preorderTraverse(TreeNode root){

    System.out.print(node.val+" ");

    preorderTraverse(root.left);

    preorderTraverse(root.right);

}

路過根節點,如果根節點為空,返回;否則,中序遍歷左子樹,然後遍歷根節點,再中序遍歷右子樹。

public void inorderTraverse(TreeNode root){

    inorderTraverse(root.left);

    System.out.print(node.val+" ");

    inorderTraverse(root.right);

}

路過根節點,如果根節點為空,返回;否則,後序遍歷左子樹,再後序遍歷右子樹,最後遍歷根節點。

public void postorderTraverse(TreeNode root){

    postorderTraverse(root.left);

    postorderTraverse(root.right);

    System.out.print(node.val+" ");

}


我們使用迭代的思想,其實就是利用循環和棧來模擬遞歸的操作,上面遞歸的操作,其實就是一個不斷將自己以及左右子節點進行壓棧和出棧的過程,如果理解了上面的算法下面的算法就好理解了

前序遍歷

public List<Integer> preorderTraversal(TreeNode root) {

    List<Integer> list = new ArrayList<>();

    if(root==null){

        return list;

    }

    Stack<TreeNode> stack = new Stack<>();

    stack.push(root);

    while(!stack.isEmpty()){

        TreeNode res = stack.pop();

        if(res.right != null)

            stack.push(res.right);

        if(res.left != null)

            stack.push(res.left);

        list.add(res.val);



    }

    return list;

}

public List<Integer> inorderTraversal(TreeNode root) {

    List<Integer> list = new ArrayList<>();

    if(root==null){

        return list;

    }

    Stack<TreeNode> stack = new Stack<>();

    TreeNode curr = root;

    while(curr != null || !(stack.isEmpty())){

        if(curr!= null){

            stack.push(curr);

            curr = curr.left;

        }else{

            curr = stack.pop();

            list.add(curr.val);

            curr = curr.right;

        }

    }

    return list;

}

我們可以很簡單的實現另一種遍歷:」根->右->左「遍歷。雖然這種遍歷沒有名字,但是他是後序遍歷的反序。所以我們可以利用兩個棧,利用棧的LIFO特點,來實現後續遍歷。

public List<Integer> preorderTraversal(TreeNode root) {

    List<Integer> list = new ArrayList<>();

    if(root==null){

        return list;

    }

    Stack<TreeNode> stack = new Stack<>();

    stack.push(root);

    while(!stack.isEmpty()){

        TreeNode res = stack.pop();

        if(res.left != null)

            stack.push(res.left);

        if(res.right != null)

        stack.push(res.right);

        list.add(res.val);



    }

    list.reserve();

    return list;

}

其實,二叉樹的先序遍歷,中序遍歷,後序遍歷,都是深度優先搜索,深搜是一種思想,並不具體指代實現方式,你可以使用遞歸,也可以使用棧來實現,所以上面提到的都是深度優先搜索的實現方式,畢竟「深度優先」嘛。

那在這裡我就是提幾個實際的應用的例子,加深一下印象。

二叉樹的最大深度

public int maxDepth(TreeNode root) {

    if(root==null){

        return 0;

    }

    int left = maxDepth(root.left);

    int right = maxDepth(root.right);

    return Math.max(left,right)+1;

}

public void Mirror(TreeNode root) {

    if(root!=null){

        if(root.left!=null || root.right!= null){

            TreeNode temp =root.left;

            root.left=root.right;

            root.right=temp;

        }

        Mirror(root.left);

        Mirror(root.right);

    } 

}

boolean isSymmetrical(TreeNode pRoot){

    if(pRoot == null)

        return true;

    return real(pRoot.left,pRoot.right);

}

public boolean real(TreeNode root1,TreeNode root2){

    if(root1 == null && root2 == null){

        return true;

    }

    if(root1 ==null || root2 == null){

        return false;

    }

    if(root1.val != root2.val){

        return false;

    }

    return real(root1.left,root2.right)&&real(root1.right,root2.left);

}

路徑總和

public class Solution {

    private ArrayList<Integer> list = new ArrayList<Integer>();

    private ArrayList<ArrayList<Integer>> listAll = new ArrayList<ArrayList<Integer>>();

    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {

        if(root == null)

            return listAll;

        list.add(root.val);

        target -= root.val;

        if(target == 0 && root.left==null && root.right == null){

            listAll.add(new ArrayList<Integer>(list));

        }

        FindPath(root.left,target);

        FindPath(root.right,target);

        list.remove(list.size()-1);

       return listAll;

    }

}

 public TreeNode reConstructBinaryTree(int [] pre,int [] in) {

    return reConstructBinaryTree(pre,0,pre.length-1,in,0,in.length-1);

}

public TreeNode reConstructBinaryTree(int [] pre,int startpre,int endpre,int [] in,int startin,int endin){

    if(startpre > endpre || startin > endin){

        return null;

    }

    TreeNode root = new TreeNode(pre[startpre]);

    for(int i =startin;i<=endin;i++){

        if(in[i] == pre[startpre]){

            root.left = reConstructBinaryTree(pre,startpre+1,startpre+i-startin,in,startin,i-1);

            root.right = reConstructBinaryTree(pre,startpre+i-startin+1,endpre,in,i+1,endin);

        }

    }

    return root;

}

class Solution {

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {

        if(root == null || root == p || root == q){

            return root;

        }

        TreeNode left = lowestCommonAncestor(root.left,p,q);

        TreeNode right = lowestCommonAncestor(root.right,p,q);

        if(left!=null && right!=null){

            return root;

        }

        return left!=null?left:right;

    }

}

序列化:

public String serialize(TreeNode root) {

    if (root == null) {

        return null;

    }

    // 利用二叉樹的層次遍歷方式進行序列化

    StringBuilder res = new StringBuilder();

    LinkedList<TreeNode> queue = new LinkedList<>();

    queue.add(root);

    while (!queue.isEmpty()) {

        TreeNode node = queue.remove();

        if (node != null) {

            res.append(node.val).append(",");

            queue.add(node.left);

            queue.add(node.right);

        } else {

            res.append("null,");

        }

    }

    return res.toString();

}

反序列化:

public TreeNode deserialize(String data) {

    if (data == null || data.length() == 0) {

        return null;

    }

    String[] dataArr = data.split(",");

    // 層次遍歷逆向還原二叉樹

    int index = 0;

    TreeNode root = toNode(dataArr[index]);

    LinkedList<TreeNode> queue = new LinkedList<>();

    queue.add(root);

    while (index < dataArr.length - 2 && !queue.isEmpty()) {

        TreeNode cur = queue.remove();

        // 添加左子節點

        TreeNode leftNode = toNode(dataArr[++index]);

        cur.left = leftNode;

        // 隊列中的節點用於為其賦值孩子節點,若該節點本身為 null,

        // 沒有孩子節點,便不再添加到隊列中,下同理

        if (leftNode != null) {

            queue.add(leftNode);

        }

        // 添加右子節點

        TreeNode rightNode = toNode(dataArr[++index]);

        cur.right = rightNode;

        if (rightNode != null) {

            queue.add(rightNode);

        }

    }

    return root;

}



private TreeNode toNode(String val) {

    if (!"null".equals(val)) {

        return new TreeNode(Integer.parseInt(val));

    } else {

        return null;

    }

}

首先將根節點放入隊列中。

從隊列中取出第一個節點,並檢驗它是否為目標。

如果找到目標,則結束搜索並回傳結果。

否則將它所有尚未檢驗過的直接子節點加入隊列中。

若隊列為空,表示整張圖都檢查過了——亦即圖中沒有欲搜索的目標。結束搜索並回傳「找不到目標」。

重複步驟2。

public List<List<Integer>> levelOrder(TreeNode root) {

    List<List<Integer>> res = new ArrayList<List<Integer>>();

    List<TreeNode> quene = new ArrayList<TreeNode>();

    if(root == null){

        return res;

    }

    quene.add(root);

    while(quene.size()!=0){

        int count = quene.size();

        List<Integer> list = new ArrayList<Integer>();

        while(count>0){

            TreeNode temp =quene.remove(0);                   

            list.add(temp.val);

            if(temp.left!=null){

                quene.add(temp.left);

            }

            if(temp.right!=null){

                quene.add(temp.right);

            } 

            count--;

        }

        res.add(list);

    }

    return res;

}

通常我們對於二叉樹進行遍歷時,使用遞歸遍歷或是基於棧來遍歷,這兩種方法都擁有最差為O(n)的空間複雜度(遞歸方法會在遞歸調用上浪費更多的時間),以及O(n)的時間複雜度。對於時間複雜度來說,由於需要遍歷每個元素一次,所以O(n)已是最優情況。如此只能對空間進行優化。Morris遍歷如何做到的呢?首先我們需要分析遞歸和基於棧的遍歷它們為什麼有O(n)的空間佔用。以下圖這個簡單的二叉樹遍歷為例:

從上面分析可以得知,傳統遍歷利用空間存儲未實現全部操作的父節點,比如對於1節點,一開始進行L操作,沒有進行D、R操作所以需要存儲起來。為解決這一問題,Morris算法用到了」線索二叉樹」的概念,利用葉節點的左右空指針指向某種遍歷順序的前驅節點或後繼節點。Morris算法中序遍歷流程:

設置節點1為Current節點;

Current節點不為空,且有左孩子,於是找到節點1左子樹中的最右側節點,即節點5,使其右孩子指針指向自己,即link1;

Current節點移動到左孩子節點2,並刪除父節點的左指針,使其指向為null,即刪除erase1;

節點2不為空,且有左孩子,於是找到節點2左子樹中最右側節點,即節點4,使其右孩子指針指向自己,即link2;

Current節點移動到左孩子節點4,並刪除父節點的左指針,使其指向為null,即刪除erase2;

節點4左孩子為空,輸出節點4,移動到右孩子節點2;

節點2無左孩子(指針指向null),輸出節點2,移動到右孩子節點5;

節點5無左孩子,輸出節點5,移動到右孩子節點1;

節點2無左孩子(指針指向null),輸出節點1,移動到右孩子節點3;

void Morris_inorderTraversal(TreeNode root) {

    TreeNode curr = root;

    TreeNode pre;

    while (curr != null) {   

        if (curr.left == null) {    // 左孩子為空

            System.out.print(curr.val+" ");

            curr = curr.right; 

        }

        else {   // 左孩子不為空

            // 找左子樹中的最右節點

            pre = curr.left; 

            while (pre.right != null) {  

                pre = pre.right;

            }

            // 刪除左孩子,防止循環

            pre.right = curr; 

            TreeNode temp = curr; 

            curr = curr.left; 

            temp.left = null;

        }

    }

}

AVL 樹是一種平衡二叉樹,平衡二叉樹遞歸定義如下:

左右子樹的高度差小於等於 1。

其每一個子樹均為平衡二叉樹。

為了保證二叉樹的平衡, AVL 樹引入了所謂監督機制,就是在樹的某一部分的不平衡度超過一個閾值後觸發相應的平衡操作。保證樹的平衡度在可以接受的範圍內。既然引入了監督機制,我們必然需要一個監督指標,以此來判斷是否需要進行平衡操作。這個監督指標被稱為「平衡因子(Balance Factor)」。定義如下:

基於平衡因子,我們就可以這樣定義 AVL 樹。

為了計算平衡因子,我們自然需要在節點中引入高度這一屬性。在這裡,我們把節點的高度定義為其左右子樹的高度的最大值。因此,引入了高度屬性的 AVL 樹的節點定義如下:

public class TreeNode {

      int val;

      int height;

      TreeNode left;

      TreeNode right;

      TreeNode(int x) { val = x; }

}

這裡的節點和上面的不同的地方在於,我們多加了一個高度,用來記錄每個節點的高度,如何得到每個節點的高度很簡單,前面講的算法中任何一種思路都可以實現,我這裡就不贅述了,不過這裡要多說一點的是,與之對應地,我們在進行如下操作時需要更新受影響的所有節點的高度:

我們重新定義了節點之後,有了高度屬性,計算平衡因子的操作就得以很簡單的實現,也就是某個節點的平衡因子=左節點高度-右節點高度。

當平衡因子的絕對值大於 1 時,就會觸發樹的修正,或者說是再平衡操作。

樹的平衡化操作

二叉樹的平衡化有兩大基礎操作:左旋和右旋。左旋,即是逆時針旋轉;右旋,即是順時針旋轉。這種旋轉在整個平衡化過程中可能進行一次或多次,這兩種操作都是從失去平衡的最小子樹根結點開始的(即離插入結點最近且平衡因子超過1的祖結點)。其中,右旋操作示意圖如下

所謂右旋操作,就是把上圖中的 B 節點和 C 節點進行所謂「父子交換」。在僅有這三個節點時候,是十分簡單的。但是當 B 節點處存在右孩子時,事情就變得有點複雜了。我們通常的操作是:拋棄右孩子,將之和旋轉後的節點 C 相連,成為節點 C 的左孩子。這樣,對應的代碼如下。

TreeNode treeRotateRight(TreeNode root) {

    TreeNode left = root.left;



    root.left = left.right; // 將將要被拋棄的節點連接為旋轉後的 root 的左孩子

    left.right = root; // 調換父子關係



    left.height = Math.max(treeHeight(left.left), treeHeight(left.right))+1;

    right.height = Math.max(treeHeight(right.left), treeHeight(right.right))+1;



    return left;

}

左旋操作和右旋操作十分類似,唯一不同的就是需要將左右互換下。我們可以認為這兩種操作是對稱的。代碼如下:

TreeNode treeRotateLeft(TreeNode root) {

    TreeNode right = root.ight;



    root.right = right.left;

    right.left = root;



    left.height = Math.max(treeHeight(left.left), treeHeight(left.right))+1;

    right->height = Math.max(treeHeight(right.left), treeHeight(right.right))+1;



    return right;

}

需要平衡的四種情況

所謂 LL 型就是上圖左邊那種情況,即因為在根節點的左孩子的左子樹添加了新節點,導致根節點的平衡因子變為 +2,二叉樹失去平衡。對於這種情況,對節點 n 右旋一次即可。

RR 型的情況和 LL 型完全對稱。只需要對節點 n 進行一次左旋即可修正。

LR 就是將新的節點插入到了 n 的左孩子的右子樹上導致的不平衡的情況。這時我們需要的是先對 i 進行一次左旋再對 n 進行一次右旋。

RL 就是將新的節點插入到了 n 的右孩子的左子樹上導致的不平衡的情況。這時我們需要的是先對 i 進行一次右旋再對 n 進行一次左旋。

這四種情況的判斷很簡單。我們根據破壞樹的平衡性(平衡因子的絕對值大於 1)的節點以及其子節點的平衡因子來判斷平衡化類型。

平衡化操作的實現如下:

int treeGetBalanceFactor(TreeNode root) {

    if(root == NULL)

        return 0;

    else

        return x.left.height - x.right.height;

}



TreeNode treeRebalance(TreeNode root) {

    int factor = treeGetBalanceFactor(root);

    if(factor > 1 && treeGetBalanceFactor(root.left) > 0) // LL

        return treeRotateRight(root);

    else if(factor > 1 && treeGetBalanceFactor(root.left) <= 0) { //LR

        root.left = treeRotateLeft(root.left);

        return treeRotateRight(temp);

    } else if(factor < -1 && treeGetBalanceFactor(root.right) <= 0) // RR

        return treeRotateLeft(root);

    else if((factor < -1 && treeGetBalanceFactor(root.right) > 0) { // RL

        root.right = treeRotateRight(root.right);

        return treeRotateLeft(root);

    } else { // Nothing happened.

        return root;

    }

}

這裡推薦一個AVL樹動態化的網站,可以通過動態可視化的方式理解AVL:https://www.cs.usfca.edu/~galles/visualization/AVLtree.html

原文連結:
https://blog.csdn.net/DBC_121/article/details/104584060

聲明:本文經授權轉自公眾號「CSDN」,ID:CSDNnews,本文系CSDN博主原創文章,版權歸作者所有。

相關焦點

  • 不怕面試被問了!二叉樹算法大盤點 | 原力計劃
    樹結構對於程式設計師來說應該不陌生,特別是二叉樹,基本只要接觸算法這一類的都一定會碰到的,所以我打算通過一篇文章,對二叉樹結構的相關算法進行總結匯總
  • 【算法總結】五道常見的算法-二叉樹
    前言前段時間,寫了面試必備的一系列文章,反應還不錯。有一些讀者反饋說,能不能整理一些面試常見的算法。前段時間,我恰好總結了 LeetCode 常見的面試算法題目。https://github.com/gdutxiaoxu/Android_interview剛開始準備刷算法題目的時候,感覺真的是好難,十道題目有九道是不會的。
  • 二叉樹算法,你懂嗎?
    聊聊面試點:一、樹 & 二叉樹樹的組成為節點和邊,節點用來儲存元素。節點組成為根節點、父節點和子節點。如圖:樹深 length 為 4;根節點的值為 5 ;父子節點關係:值為 8 和 值為 3 的節點
  • 手寫二叉樹?程式設計師面試最常見問題TOP 48
    你在申請這些工作時,肯定很想知道面試官會問到哪些問題。在本文中,作者會分享一些常見的編程面試問題,這些問題來自於針對不同經驗層次的程式設計師的面試——從應屆畢業生到具有一兩年經驗的程式設計師。編程面試題通常包含數據結構和基於算法的問題,以及一些邏輯問題,例如:如何在不使用臨時變量的情況下交換兩個整數?為了清晰,編程面試題需要劃分為不同主題。
  • TOP 48 算法和編程面試題,牛逼啊!
    你在申請這些工作時,肯定很想知道面試官會問到哪些問題。在本文中,作者會分享一些常見的編程面試問題,這些問題來自於針對不同經驗層次的程式設計師的面試——從應屆畢業生到具有一兩年經驗的程式設計師。編程面試題通常包含數據結構和基於算法的問題,以及一些邏輯問題,例如:如何在不使用臨時變量的情況下交換兩個整數?為了清晰,編程面試題需要劃分為不同主題。
  • 憑藉清華掃地僧的路線指引,從Java基礎到算法,吊打阿里面試官!
    字節跳動面試總共是3+1面試(技術3面+HR1面),三面技術具體問了什麼題目我是有點分不清了,不過我記得每個知識點大概問了那些問題,大致就是分為Java架構基礎+Redis+Linux/作業系統+HTTPS+MySQL資料庫+算法這六個部分吧,不過話說回來,這次之所以能僥倖過關,多虧了清華掃地僧大佬的學習路線,自己也下了大功夫,也算是功夫不負有心人吧~~~
  • 實戰LeetCode - 前端面試必備二叉樹算法
    二叉樹簡介二叉樹是一種非常重要的數據結構,很多其它數據結構都是基於二叉樹的基礎演變而來的。對於二叉樹,有深度遍歷和廣度遍歷,深度遍歷有前序、中序以及後序三種遍歷方法,廣度遍歷即我們平常所說的層次遍歷。因為樹的定義本身就是遞歸定義,因此採用遞歸的方法去實現樹的三種遍歷不僅容易理解而且代碼很簡潔,而對於廣度遍歷來說,需要其他數據結構的支撐,比如堆了。
  • 面試經驗分享之數據結構、算法題
    面試不僅是考驗求職者,也同樣在考驗面試官,如果問的都是老題兒,那本山大叔肯定都會搶答了。言歸正傳,以下分數據結構題目、算法題目、開放題目三部分來介紹我在面試中碰到的問題。數據結構題目概述雖然課本由簡到繁、由難到易地介紹了諸多數據結構,我在面試中被問到的卻還都是基本類型,比如堆棧、隊列、鍊表、二叉樹。題目主要有兩類,數據結構實現和具體情境下數據結構的應用。
  • 看完這篇,再也不怕面試問樹了!
    作為一個應用非常廣的數據結構,不僅在工作中常用,在面試中也非常常考。一是因為樹的結構天然決定了它和遞歸聯繫緊密,很多樹相關的算法題都非常適合用遞歸來解;二是因為它的難度介於鍊表和圖之間,非常適合在 45 分鐘的面試裡進行考察,所以一場面試中遇到兩三輪問樹都是再正常不過的了。
  • 乾貨 | 算法和編程面試題精選TOP50!(附代碼+解題思路+答案)
    這次營長表示要翻 Java 的牌子啦~ 應大家的強烈反饋,我們找了一套 Java 語言的算法和編程的面試題。這份面試資源主要包含五部分內容:數組、鍊表、字符串、二叉樹和重要算法(如排序算法)的編程面試題,其中每部分內容我們都列出了一些最常被問到的熱門問題,並且在每個題目後給出了可以參考的解決思路和代碼,因為題目較多,我們沒有羅列所有的方法和代碼,只給出了訪問地址。相信大家在掌握了這些內容後,一定可以提升實力、信心大增。
  • 二叉樹的常用算法
    節點訪問的次序,忽略列印行為如果將列印安排在同個數字第一次被訪問時,第一次即先序遍歷第二次即中序遍歷第三次即後序遍歷現二叉樹的先序、中序、後序遍歷,包括遞歸方式和非遞歸方式二叉樹結構定義public static class Node {        public
  • 【面試錦囊】14種模式搞定面試算法編程題(1-7)
    為了更方便大家的交流溝通,我們建立了算法面試討論組,感興趣的小夥伴可以訂閱後臺回復"面試"加入好了不廢話啦,今天文章的主題就是分享14種解決面試算法編程題的思路(來自educative[1]),經過本人之前筆試面試經驗證明確實確實非常非常高頻,一定要十分熟悉。
  • 如何從菜雞變成收割機,大廠面試的算法,你懂了嗎?
    讓大廠面試顯得逼格很高,是算法和數據結構嗎?是的!!!Google工程師曾總結過,大廠之所以愛考察算法和數據結構是因為:算法能力能夠準確辨別一個程式設計師的技術功底是否紮實;算法能力是發掘程式設計師的學習能力與成長潛力的關鍵手段;算法能力能夠協助判斷程式設計師在面對新問題時,分析並解決問題的能力;算法能力是設計一個高性能系統、性能優化的必備基礎。
  • 通過一道面試題目,講一講遞歸算法的時間複雜度!
    可以和面試官探討一下,詢問:「可不可以給點提示」。面試官提示:「考慮一下遞歸算法」。那麼就可以寫出了如下這樣的一個遞歸的算法,使用遞歸解決了這個問題。面試官問:「那麼這個代碼的時間複雜度是多少?」。(x, n / 2)*x;    }    return function3(x, n / 2) * function3(x, n / 2);}面試官看到後微微一笑,問:「這份代碼的時間複雜度又是多少呢?」
  • 看了這篇文章,再也不怕面試問樹了(文末福利)
    作為一個應用非常廣的數據結構,不僅在工作中常用,在面試中也常考。一是因為樹的結構天然決定了它和遞歸聯繫緊密,很多樹相關的算法題都非常適合用遞歸來解;二是因為它的難度介於鍊表和圖之間,非常適合在 45 分鐘的面試裡進行考察,所以一場面試中遇到兩三輪問樹都是再正常不過的了。
  • 14種模式搞定面試算法編程題(PART I)
    為了更方便大家的交流溝通,我們建立了算法面試討論組,感興趣的小夥伴可以訂閱後臺回復"面試"加入好了不廢話啦,今天文章的主題就是分享14種解決面試算法編程題的思路(來自educative[1]),經過本人之前筆試面試經驗證明確實確實非常非常高頻,一定要十分熟悉。
  • 資源 | 從算法到數據結構,百道面試問題實現答案集合
    、數據結構以及面試問題解決方案的集合,裡面的 repository 包含了我對常見算法問題的解答以及數據結構的實現(用 Java)。項目地址:https://github.com/sherxon/AlgoDS目前為止,該資源集合提供了算法、數據結構以及 200 道面試題的答案。
  • 二叉樹和分治法
    root->any)Binary Tree Maximum Path Sum (any->any)二叉查找樹二叉樹的寬度優先搜索1.二叉樹的遍歷這個應該是二叉樹裡面最基本的題了,但是在面試過程中,不一定會考遞歸的方式,很有可能會讓你寫出非遞歸的方法,應該直接把非遞歸的方法背下來
  • 二叉樹的前序非遞歸遍歷
    二叉樹的遍歷是二叉樹數據結構的重要操作之一,對於遍歷我們首先想到的就是遞歸,遞歸操作思想簡潔,操作簡單,但是在複雜的高度較大的二叉樹應用中使用遞歸算法,可能會造成性能的浪費,本文來介紹二叉樹的前序非遞歸遍歷。
  • 【原創教程】數據結構與算法(1)——二叉樹
    一、二叉樹二叉樹是一種更為典型的樹樹狀結構。