數據結構丨二叉樹


樹的遍歷

樹的遍歷-介紹

前序遍歷

前序遍歷首先訪問根節點,然后遍歷左子樹,最后遍歷右子樹。

請看下面的例子:

前序

中序遍歷

中序遍歷是先遍歷左子樹,然后訪問根節點,然后遍歷右子樹。

讓我們一起來看樹的中序遍歷:

中序

后序遍歷

后序遍歷是先遍歷左子樹,然后遍歷右子樹,最后訪問樹的根節點。

我們一起來看后序遍歷的動畫演示:

后序

值得注意的是,當你刪除樹中的節點時,刪除過程將按照后序遍歷的順序進行。 也就是說,當你刪除一個節點時,你將首先刪除它的左節點和它的右邊的節點,然后再刪除節點本身。

另外,后序在數學表達中被廣泛使用。 編寫程序來解析后綴表示法更為容易。 這里是一個例子:

1562733446955

您可以使用中序遍歷輕松找出原始表達式。 但是程序處理這個表達式時並不容易,因為你必須檢查操作的優先級。

如果你想對這棵樹進行后序遍歷,使用棧來處理表達式會變得更加容易。 每遇到一個操作符,就可以從棧中彈出棧頂的兩個元素,計算並將結果返回到棧中。

遞歸和迭代

請練習文章后面習題中的三種遍歷方法。 您可以通過遞歸或迭代方法實現算法,並比較它們之間的差異。

二叉樹的前序遍歷

給定一個二叉樹,返回它的 前序 遍歷。

示例:

輸入: [1,null,2,3]  
   1
    \
     2
    /
   3 

輸出: [1,2,3]

進階: 遞歸算法很簡單,你可以通過迭代算法完成嗎?

#include <iostream>
#include <vector>
#include <stack>

using namespace std;

struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x):val(x),left(NULL),right(NULL){}
};

// Recursive
// Time Complexity: O(n), n is the node number in the tree
// Space Complexity: O(h), h is the height of the tree
class SolutionA{
public: 
    vector<int> preorderTraversal(TreeNode* root){
        vector<int> res;
        preorderTraversal(root, res);
        return res;
    }
private: 
    void preorderTraversal(TreeNode* node, vector<int> &res){
        if(node){
            res.push_back(node->val);
            preorderTraversal(node->left, res);
            preorderTraversal(node->right, res);
        }
    }
};

// Classic Non-Recursive algorithm for preorder traversal
// Time Complexity: O(n), n is the node number in the tree
// Space Complexity: O(h), h is the height of the tree
class SolutionB{
public: 
    vector<int> preorderTraversal(TreeNode* root){
        vector<int> res;
        if(root == NULL);
            return res;
        
        stack<TreeNode*> stack;
        stack.push(root);
        while(!stack.empty()){
            TreeNode* curNode = stack.top();
            stack.pop();
            res.push_back(curNode->val);

            if(curNode->right)
                stack.push(curNode->right);
            if(curNode->left)
                stack.push(curNode->left);
        }
        return res;
    }
}
int main(){
    return 0;
}

二叉樹的中序遍歷

給定一個二叉樹,返回它的中序 遍歷。

示例:

輸入: [1,null,2,3]
   1
    \
     2
    /
   3

輸出: [1,3,2]

進階: 遞歸算法很簡單,你可以通過迭代算法完成嗎?

#include <iostream>
#include <vector>
#include <stack>

using namespace std;

/// Definition for a binary tree node.
struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x):val(x), left(NULL), right(NULL){}
};

// Recursive
// Time Complexity: O(n), n is the node number in the tree
// Space Complexity: O(h), h is the height of the tree
class SolutionA{
public: 
    vector<int> inorderTraversal(TreeNode* root){
        vector<int> res;
        __inorderTraversal(root, res);
        return res;
    }
private:
    void __inorderTraversal(TreeNode* node, vector<int>& res){
        if(node){
            __inorderTraversal(node->left, res);
            res.push_back(node->val);
            __inorderTraversal(node->right, res);
        }
    }
};

// Classic Non-Recursive algorithm for inorder traversal
// Time Complexity: O(n), n is the node number in the tree
// Space Complexity: O(h), h is the height of the tree
class SolutionB{
public: 
    vector<int> inorderTraversal(TreeNode* root){
        vector<int> res;
        if(root == NULL)
            return res;
        
        stack<TreeNode*> stack;
        TreeNode* cur = root;
        while(cur != NULL || !stack.empty()){
            //先到達左下端
            while(cur != NULL){
                stack.push(cur);
                cur = cur->left;
            }
            cur = stack.top();
            stack.pop();
            res.push_back(cur->val);
            cur = cur->right;
        }
        return res;
    }
};

二叉樹的后序遍歷

給定一個二叉樹,返回它的 后序 遍歷。

示例:

輸入: [1,null,2,3]  
   1
    \
     2
    /
   3 

輸出: [3,2,1]

進階: 遞歸算法很簡單,你可以通過迭代算法完成嗎?

#include <iostream>
#include <vector>
#include <stack>

using namespace std;

struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x): val(x), left(NULL), right(NULL){}
};

// Recursive
// Time Complexity: O(n), n is the node number in the tree
// Space Complexity: O(h), h is the height of the tree
class SolutionA{
public: 
    vector<int> postorderTraversal(TreeNode* root){
        vector<int> res;
        __postorderTraversal(root, res);
        return res;
    }
private:
    void __postorderTraversal(TreeNode* node, vector<int> &res){
        if(node){
            __postorderTraversal(node->left, res);
            __postorderTraversal(node->right, res);
            res.push_back(node->val);
        }
    }
};

// Classic Non-Recursive
// Using a pre pointer to record the last visted node
//
// Time Complexity: O(n)
// Space Complexity: O(h)
class SolutionB{
public: 
    vector<int> postorderTraversal(TreeNode* root){
        vector<int> res;
        if(root == NULL)
            return res;
        stack<TreeNode*> stack;
        TreeNode* pre = NULL;
        TreeNode* cur = root;

        while(cur != NULL || !stack.empty()){
            //到達最左下端
            while(cur != NULL){
                stack.push(cur);
                cur = cur->left;
            }
            cur = stack.top();
            stack.pop();

            //確保該節點沒有右子樹或者右子樹已遍歷過
            if(cur->right == NULL || pre == cur->right){
                res.push_back(cur->val);
                pre = cur;
                cur = NULL;
            }
            else{
                stack.push(cur);
                cur = cur->right;
            }
        }
        return res;
    }
};

層次遍歷-介紹

層序遍歷就是逐層遍歷樹結構。

廣度優先搜索是一種廣泛運用在樹或圖這類數據結構中,遍歷或搜索的算法。 該算法從一個根節點開始,首先訪問節點本身。 然后遍歷它的相鄰節點,其次遍歷它的二級鄰節點、三級鄰節點,以此類推。

當我們在樹中進行廣度優先搜索時,我們訪問的節點的順序是按照層序遍歷順序的。

這是一個層序順序遍歷的例子:

層次

通常,我們使用一個叫做隊列的數據結構來幫助我們做廣度優先搜索。 如果您對隊列不熟悉,可以在我們即將推出的另一張卡片中找到更多有關信息。

二叉樹的層次遍歷

給定一個二叉樹,返回其按層次遍歷的節點值。 (即逐層地,從左到右訪問所有節點)。

例如:
給定二叉樹: [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

返回其層次遍歷結果:

[
  [3],
  [9,20],
  [15,7]
]
#include <iostream>
#include <vector>
#include <queue>
#include <cassert>

using namespace std;

struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x): val(x), left(NULL), right(NULL){}
};

/// BFS
/// Time Complexity: O(n), where n is the number of nodes in the tree
/// Space Complexity: O(n)
class Solution{
public: 
    vector<vector<int>> levelOrder(TreeNode* root){
        vector<vector<int>> res;
        if(root == NULL)
            return res;

        queue<pair<TreeNode*, int>> q;
        q.push(make_pair(root, 0));

        while(!q.empty()){
            TreeNode* node = q.front().first;
            int level = q.front().second;
            q.pop();

            if(level == res.size())
                res.push_back(vector<int>());
            assert(level <res.size());

            res[level].push_back(node->val);
            if(node->left)
                q.push(make_pair(node->left, level+1));
            if(node->right)
                q.push(make_pair(node->right, level+1));
        }
        return res;
    }
};

運用遞歸解決問題

運用遞歸解決樹的問題

在前面的章節中,我們已經介紹了如何利用遞歸求解樹的遍歷。 遞歸是解決樹的相關問題最有效和最常用的方法之一。

我們知道,樹可以以遞歸的方式定義為一個節點(根節點),它包括一個值和一個指向其他節點指針的列表。 遞歸是樹的特性之一。 因此,許多樹問題可以通過遞歸的方式來解決。 對於每個遞歸層級,我們只能關注單個節點內的問題,並通過遞歸調用函數來解決其子節點問題。

通常,我們可以通過 “自頂向下” 或 “自底向上” 的遞歸來解決樹問題。

“自頂向下” 的解決方案

“自頂向下” 意味着在每個遞歸層級,我們將首先訪問節點來計算一些值,並在遞歸調用函數時將這些值傳遞到子節點。 所以 “自頂向下” 的解決方案可以被認為是一種前序遍歷。 具體來說,遞歸函數 top_down(root, params) 的原理是這樣的:

1. return specific value for null node
2. update the answer if needed                      // anwer <-- params
3. left_ans = top_down(root.left, left_params)      // left_params <-- root.val, params
4. right_ans = top_down(root.right, right_params)   // right_params <-- root.val, params
5. return the answer if needed           

例如,思考這樣一個問題:給定一個二叉樹,請尋找它的最大深度。

我們知道根節點的深度是1。 對於每個節點,如果我們知道某節點的深度,那我們將知道它子節點的深度。 因此,在調用遞歸函數的時候,將節點的深度傳遞為一個參數,那么所有的節點都知道它們自身的深度。 而對於葉節點,我們可以通過更新深度從而獲取最終答案。 這里是遞歸函數 maximum_depth(root, depth) 的偽代碼:

1. return if root is null
2. if root is a leaf node:
3.      answer = max(answer, depth)         // update the answer if needed
4. maximum_depth(root.left, depth + 1)      // call the function recursively for left child
5. maximum_depth(root.right, depth + 1)     // call the function recursively for right child

以下的例子可以幫助你理解它是如何工作的:

top2down

“自底向上” 的解決方案

“自底向上” 是另一種遞歸方法。 在每個遞歸層次上,我們首先對所有子節點遞歸地調用函數,然后根據返回值和根節點本身的值得到答案。 這個過程可以看作是后序遍歷的一種。 通常, “自底向上” 的遞歸函數 bottom_up(root) 為如下所示:

1. return specific value for null node
2. left_ans = bottom_up(root.left)          // call function recursively for left child
3. right_ans = bottom_up(root.right)        // call function recursively for right child
4. return answers                           // answer <-- left_ans, right_ans, root.val

讓我們繼續討論前面關於樹的最大深度的問題,但是使用不同的思維方式:對於樹的單個節點,以節點自身為根的子樹的最大深度x是多少?

如果我們知道一個根節點,以其子節點為根的最大深度為l和以其子節點為根的最大深度為r,我們是否可以回答前面的問題? 當然可以,我們可以選擇它們之間的最大值,再加上1來獲得根節點所在的子樹的最大深度。 那就是 x = max(l,r)+ 1

這意味着對於每一個節點來說,我們都可以在解決它子節點的問題之后得到答案。 因此,我們可以使用“自底向上“的方法。下面是遞歸函數 maximum_depth(root) 的偽代碼:

1. return 0 if root is null                 // return 0 for null node
2. left_depth = maximum_depth(root.left)
3. right_depth = maximum_depth(root.right)
4. return max(left_depth, right_depth) + 1  // return depth of the subtree rooted at root

以下的例子可以幫助你理解它是如何工作的:

down2top

總結

了解遞歸並利用遞歸解決問題並不容易。

當遇到樹問題時,請先思考一下兩個問題:

  1. 你能確定一些參數,從該節點自身解決出發尋找答案嗎?
  2. 你可以使用這些參數和節點本身的值來決定什么應該是傳遞給它子節點的參數嗎?

如果答案都是肯定的,那么請嘗試使用 “自頂向下” 的遞歸來解決此問題。

或者你可以這樣思考:對於樹中的任意一個節點,如果你知道它子節點的答案,你能計算出該節點的答案嗎? 如果答案是肯定的,那么 “自底向上” 的遞歸可能是一個不錯的解決方法。

在接下來的章節中,我們將提供幾個經典例題,以幫助你更好地理解樹的結構和遞歸。

二叉樹的最大深度

給定一個二叉樹,找出其最大深度。

二叉樹的深度為根節點到最遠葉子節點的最長路徑上的節點數。

說明: 葉子節點是指沒有子節點的節點。

示例:
給定二叉樹 [3,9,20,null,null,15,7]

    3
   / \
  9  20
    /  \
   15   7

返回它的最大深度 3 。

#include <iostream>

using namespace std;

struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x): val(x), left(NULL), right(NULL){}
};

/// Recursive
/// Time Complexity: O(n), where n is the nodes' number in the tree
/// Space Complexity: O(h), where h is the height of the tree
class Solution{
public: 
    int maxDepth(TreeNode* root){
        if(root == NULL)
            return 0;
        return 1+max(maxDepth(root->left), maxDepth(root->right));
    }
};

int main(){
    return 0;
}

對稱二叉樹

給定一個二叉樹,檢查它是否是鏡像對稱的。

例如,二叉樹 [1,2,2,3,4,4,3] 是對稱的。

    1
   / \
  2   2
 / \ / \
3  4 4  3

但是下面這個 [1,2,2,null,3,null,3] 則不是鏡像對稱的:

    1
   / \
  2   2
   \   \
   3    3

說明:

如果你可以運用遞歸和迭代兩種方法解決這個問題,會很加分。

#include <iostream>
#include <queue>

using namespace std;

/// Definition for a binary tree node.
struct TreeNode
{
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

/// Recursive
/// No need to revert one child tree
/// See if the two child trees of the root are mirror directly
///
/// Time Complexity: O(n)
/// Space Complexity: O(h)
class SolutionA
{
public:
    bool isSymmetric(TreeNode *root)
    {
        if (root == NULL)
            return true;
        return is_mirror(root, root);
    }

private:
    bool is_mirror(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 is_mirror(root1->left, root2->right) &&
               is_mirror(root1->right, root2->left);
    }
};

/// Non-Recursive
/// Using one queues to level traverse the root in different directions
///
/// Time Complexity: O(n)
/// Space Complexity: O(n)
class SolutionB{
public: 
    bool isSymmetric(TreeNode* root){
        if(root == NULL)
            return true; 

        queue<TreeNode*> q;
        q.push(root);
        q.push(root);
        while(!q.empty()){
            TreeNode* node1 = q.front();
            q.pop();

            TreeNode* node2 = q.front();
            q.pop();

            if(node1 == NULL && node2 == NULL)
                continue;
            
            if(node1 == NULL || node2 == NULL)
                return false;
            
            if(node1->val != node2->val)
                return false;
            
            q.push(node1->left);
            q.push(node2->right);
            q.push(node1->right);
            q.push(node2->left);

        }
        return true;
    }
};

路徑總和

給定一個二叉樹和一個目標和,判斷該樹中是否存在根節點到葉子節點的路徑,這條路徑上所有節點值相加等於目標和。

說明: 葉子節點是指沒有子節點的節點。

示例:
給定如下二叉樹,以及目標和 sum = 22

              5
             / \
            4   8
           /   / \
          11  13  4
         /  \      \
        7    2      1

返回 true, 因為存在目標和為 22 的根節點到葉子節點的路徑 5->4->11->2

#include <iostream>

using namespace std;

/// Definition for a binary tree node.
struct TreeNode
{
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

/// Recursive
/// Time Complexity: O(n), where n is the nodes' number of the tree
/// Space Complexity: O(h), where h is the height of the tree
class Solution
{
public:
    bool hasPathSum(TreeNode *root, int sum)
    {
        if (root == NULL)
            return false;
        if (root->left == NULL && root->right == NULL)
            return sum == root->val;

        return hasPathSum(root->left, sum - root->val) || hasPathSum(root->right, sum - root->val);
    }
};

int main(){
    return 0;
}

總結

從中序與后序遍歷序列構造二叉樹

根據一棵樹的中序遍歷與后序遍歷構造二叉樹。

注意:
你可以假設樹中沒有重復的元素。

例如,給出

中序遍歷 inorder = [9,3,15,20,7]
后序遍歷 postorder = [9,15,7,20,3]

返回如下的二叉樹:

    3
   / \
  9  20
    /  \
   15   7
#include <iostream>
#include <vector>
#include <cassert>
#include <algorithm>

using namespace std;

/// Definition for a binary tree node.
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};


/// Recursive
/// Time Complexity: O(n*h) where n is the num of node in th tree
///                         and h is the height of the tree
/// Space Complexity: O(h)
class Solution {
public:
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        return buildTree(inorder, 0, inorder.size(), postorder, 0, postorder.size());
    }

private:
    TreeNode* buildTree(vector<int>& inorder, int inorderL, int inorderR,
                        vector<int>& postorder, int postorderL, int postorderR){

        if(inorderL >= inorderR){
            assert(postorderL >= postorderR);
            return NULL;
        }

        if(inorderL + 1 == inorderR){
            assert(postorderL + 1 == postorderR);
            return new TreeNode(inorder[inorderL]);
        }

        TreeNode* root = new TreeNode(postorder[postorderR - 1]);
        int rootPos = find(inorder.begin() + inorderL, inorder.begin() + inorderR, root->val) - inorder.begin();
        assert(inorderL <= rootPos && rootPos < inorderR);

        int lsize = rootPos - inorderL;
        int rsize = inorderR - (rootPos + 1);
        root->left = buildTree(inorder, inorderL, inorderL + lsize, postorder, postorderL, postorderL + lsize);
        root->right = buildTree(inorder, rootPos + 1, inorderR, postorder, postorderL + lsize, postorderR - 1);
        return root;
    }
};


int main() {

    vector<int> inorder = {9,3,15,20,7};
    vector<int> postorder = {9,15,7,20,3};
    TreeNode* root = Solution().buildTree(inorder, postorder);
    printf("ok");

    return 0;
}

從前序與中序遍歷序列構造二叉樹

根據一棵樹的前序遍歷與中序遍歷構造二叉樹。

注意:
你可以假設樹中沒有重復的元素。

例如,給出

前序遍歷 preorder = [3,9,20,15,7]
中序遍歷 inorder = [9,3,15,20,7]

返回如下的二叉樹:

    3
   / \
  9  20
    /  \
   15   7
#include <iostream>
#include <vector>
#include <cassert>
#include <algorithm>

using namespace std;

/// Definition for a binary tree node.
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

/// Recursive
/// Time Complexity: O(n*h) where n is the num of node in th tree
///                         and h is the height of the tree
/// Space Complexity: O(h)
class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        return buildTree(preorder, 0, preorder.size(), inorder, 0, inorder.size());
    }

private:
    TreeNode* buildTree(const vector<int>& preorder, int preorderL, int preorderR,
                        const vector<int>& inorder, int inorderL, int inorderR){

        if(inorderL >= inorderR){
            assert(preorderL >= preorderR);
            return NULL;
        }

        if(inorderL + 1 == inorderR){
            assert(preorderL + 1 == preorderR);
            return new TreeNode(inorder[inorderL]);
        }

        TreeNode* root = new TreeNode(preorder[preorderL]); //就變化了這一行
        int rootPos = find(inorder.begin() + inorderL, inorder.begin() + inorderR, root->val) - inorder.begin();
        assert(rootPos >= inorderL && rootPos < inorderR);

        int lsize = rootPos - inorderL;
        int rsize = inorderR - (rootPos + 1);
        root->left = buildTree(preorder, preorderL + 1, preorderL + 1 + lsize, inorder, inorderL, rootPos);
        root->right = buildTree(preorder, preorderL + 1 + lsize, preorderR, inorder, rootPos + 1, inorderR);
        return root;
    }
};


int main() {

    vector<int> preorder = {3, 9, 20, 15, 7};
    vector<int> inorder = {9,3,15,20,7};
    TreeNode* root = Solution().buildTree(preorder, inorder);
    printf("ok");

    return 0;
}

填充每個節點的下一個右側節點指針

給定一個完美二叉樹,其所有葉子節點都在同一層,每個父節點都有兩個子節點。二叉樹定義如下:

struct Node {
  int val;
  Node *left;
  Node *right;
  Node *next;
}

填充它的每個 next 指針,讓這個指針指向其下一個右側節點。如果找不到下一個右側節點,則將 next 指針設置為 NULL

初始狀態下,所有 next 指針都被設置為 NULL

示例:

img

輸入:{"$id":"1","left":{"$id":"2","left":{"$id":"3","left":null,"next":null,"right":null,"val":4},"next":null,"right":{"$id":"4","left":null,"next":null,"right":null,"val":5},"val":2},"next":null,"right":{"$id":"5","left":{"$id":"6","left":null,"next":null,"right":null,"val":6},"next":null,"right":{"$id":"7","left":null,"next":null,"right":null,"val":7},"val":3},"val":1}

輸出:{"$id":"1","left":{"$id":"2","left":{"$id":"3","left":null,"next":{"$id":"4","left":null,"next":{"$id":"5","left":null,"next":{"$id":"6","left":null,"next":null,"right":null,"val":7},"right":null,"val":6},"right":null,"val":5},"right":null,"val":4},"next":{"$id":"7","left":{"$ref":"5"},"next":null,"right":{"$ref":"6"},"val":3},"right":{"$ref":"4"},"val":2},"next":null,"right":{"$ref":"7"},"val":1}

解釋:給定二叉樹如圖 A 所示,你的函數應該填充它的每個 next 指針,以指向其下一個右側節點,如圖 B 所示。

提示:

  • 你只能使用常量級額外空間。
  • 使用遞歸解題也符合要求,本題中遞歸程序占用的棧空間不算做額外的空間復雜度。
#include <iostream>
#include <queue>
#include <cassert>

using namespace std;

/// Using queue for BFS
/// Time Complexity: O(n)
/// Space Compelxity: O(n)

/// Definition for binary tree with next pointer.
struct TreeLinkNode{
    int val;
    TreeLinkNode *left, *right, *next;
    TreeLinkNode(int x): val(x), left(NULL), right(NULL), next(NULL){}
};

//層次遍歷中前一個節點的next指向后一個節點
class SolutionA{
public: 
    void connect(TreeLinkNode* root){
        if(!root) return;

        queue<TreeLinkNode*> q;
        q.push(root);
        int level = 0;
        while(!q.empty()){
            //移動幾位就是二的幾次方,跟每層的節點數目相對應
            int n = (1 << level);
            while(n --){    //遍歷每一個節點
                TreeLinkNode* cur = q.front();
                q.pop();
                if(n)   //每層的最后一個節點不作處理
                    cur->next = q.front();
                if(cur->left){
                    q.push(cur->left);
                    assert(cur->right);
                    q.push(cur->right);
                }
            }
            level ++;
        }
    }
};

/// DFS
/// Time Complexity: O(n)
/// Space Compelxity: O(logn)

/// Definition for binary tree with next pointer.
class SolutionB{
public: 
    void connect(TreeLinkNode* root){
        if(!root || !root->left) return;
        dfs(root->left, root->right);   //所有左節點的next指向右節點

        connect(root->left);
        connect(root->right);
    }
private: 
    void dfs(TreeLinkNode* l, TreeLinkNode* r){
        if(l){
            l->next = r;
            dfs(l->right, r->left); //所有右節點的next指向左節點
        }
    }
};

/// BFS without queue
/// Since the upper level have already been a linked list
/// We can traverse the upper level in a linked list way to connect the lower level
/// Actually, the upper linked list is our queue :-)
///
/// Time Complexity: O(n)
/// Space Compelxity: O(1)

/// Definition for binary tree with next pointer.
class SolutionC{
public: 
    void connect(TreeLinkNode* root){
        if(!root) return;

        while(root->left){
            TreeLinkNode* p = root;
            TreeLinkNode* dummyHead = new TreeLinkNode(-1);
            TreeLinkNode* cur = dummyHead;
            while(p){
                cur->next = p->next;
                cur = cur->next;

                cur->next = p->right;
                cur = cur->next;

                p = p->next;
            }
        }
    }
}

填充每個節點的下一個右側節點指針 II

給定一個二叉樹

struct Node {
  int val;
  Node *left;
  Node *right;
  Node *next;
}

填充它的每個 next 指針,讓這個指針指向其下一個右側節點。如果找不到下一個右側節點,則將 next 指針設置為 NULL

初始狀態下,所有 next 指針都被設置為 NULL

示例:

img

輸入:{"$id":"1","left":{"$id":"2","left":{"$id":"3","left":null,"next":null,"right":null,"val":4},"next":null,"right":{"$id":"4","left":null,"next":null,"right":null,"val":5},"val":2},"next":null,"right":{"$id":"5","left":null,"next":null,"right":{"$id":"6","left":null,"next":null,"right":null,"val":7},"val":3},"val":1}

輸出:{"$id":"1","left":{"$id":"2","left":{"$id":"3","left":null,"next":{"$id":"4","left":null,"next":{"$id":"5","left":null,"next":null,"right":null,"val":7},"right":null,"val":5},"right":null,"val":4},"next":{"$id":"6","left":null,"next":null,"right":{"$ref":"5"},"val":3},"right":{"$ref":"4"},"val":2},"next":null,"right":{"$ref":"6"},"val":1}

解釋:給定二叉樹如圖 A 所示,你的函數應該填充它的每個 next 指針,以指向其下一個右側節點,如圖 B 所示。

提示:

  • 你只能使用常量級額外空間。
  • 使用遞歸解題也符合要求,本題中遞歸程序占用的棧空間不算做額外的空間復雜度。
#include <iostream>
#include <queue>

using namespace std;

/// Using BFS
/// Time Complexity: O(n)
/// Space Complexity: O(n)

/// Definition for binary tree with next pointer.
struct Node{
    int val;
    Node* left, *right, *next;
    Node(int x): val(x), left(NULL), right(NULL), next(NULL) {}
};

//該解法和3.3的解法沒有差異
class Solution{
public: 
    Node* connect(Node* root){
        if(!root)
            return NULL;

        queue<Node*> q;
        q.push(root);
        int level_num = 1;
        while(!q.empty()){
            int new_level_num = 0;
            for(int i=0; i<level_num; i++){
                Node* node = q.front();
                q.pop();
                node->next = (i == level_num - 1 ? NULL : q.front());//用來判斷是否為最后一個節點

                if(node->left){
                    q.push(node->left);
                    new_level_num ++;
                }
                if(node->right){
                    q.push(node->right);
                    new_level_num ++;
                }
            }
            level_num = new_level_num;  //用來對下一層的節點進行計數
        }      
        return root;
    }
};

二叉樹的最近公共祖先

給定一個二叉樹, 找到該樹中兩個指定節點的最近公共祖先。

百度百科中最近公共祖先的定義為:“對於有根樹 T 的兩個結點 p、q,最近公共祖先表示為一個結點 x,滿足 x 是 p、q 的祖先且 x 的深度盡可能大(一個節點也可以是它自己的祖先)。”

例如,給定如下二叉樹: root = [3,5,1,6,2,0,8,null,null,7,4]

img

示例 1:

輸入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
輸出: 3
解釋: 節點 5 和節點 1 的最近公共祖先是節點 3。

示例 2:

輸入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
輸出: 5
解釋: 節點 5 和節點 4 的最近公共祖先是節點 5。因為根據定義最近公共祖先節點可以為節點本身。

說明:

  • 所有節點的值都是唯一的。
  • p、q 為不同節點且均存在於給定的二叉樹中。

最近是指離葉子最近,最遠公共祖先一定是根節點,無意義

#include <iostream>
#include <cassert>

using namespace std;

/// Recursion implementation
/// Time Complexity: O(n)
/// Space Complexity: O(1)

///Definition for a binary tree node.
struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x): val(x), left(NULL), right(NULL){}
};

class Solution{
public: 
    // 在root中尋找p和q
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q){
        //底部的基本情況
        if(root == NULL)    // 節點為空
            return root;
        if(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;

        //自底向上,最后返回的,自身為目標節點,且另一個目標節點在其之下。
        if(left != NULL)    //左節點非空
            return left;

        if(right != NULL)   //右節點非空
            return right;
        
        return NULL;    //左右節點都為空
    }
};

二叉樹的序列化與反序列化

序列化是將一個數據結構或者對象轉換為連續的比特位的操作,進而可以將轉換后的數據存儲在一個文件或者內存中,同時也可以通過網絡傳輸到另一個計算機環境,采取相反方式重構得到原數據。

請設計一個算法來實現二叉樹的序列化與反序列化。這里不限定你的序列 / 反序列化算法執行邏輯,你只需要保證一個二叉樹可以被序列化為一個字符串並且將這個字符串反序列化為原始的樹結構。

示例:

你可以將以下二叉樹:

    1
   / \
  2   3
     / \
    4   5

序列化為 "[1,2,3,null,null,4,5]"

提示: 這與 LeetCode 目前使用的方式一致,詳情請參閱 LeetCode 序列化二叉樹的格式。你並非必須采取這種方式,你也可以采用其他的方法解決這個問題。

說明: 不要使用類的成員 / 全局 / 靜態變量來存儲狀態,你的序列化和反序列化算法應該是無狀態的。

#include <iostream>
#include <vector>
#include <queue>
#include <cassert>

using namespace std;

/// BFS
/// Time Complexity: O(n)
/// Space Complexity: O(n)

/// Definition for a binary tree node.
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

class Codec{
public: 
    //Encodes a tree to a single string.
    string serialize(TreeNode* root){
        if(root)
            return "[null]";
        
        string ret = "[";

        queue<TreeNode*> q;
        q.push(root);
        ret += to_string(root->val);
        while(!q.empty()){
            TreeNode* cur = q.front();
            q.pop();

            if(cur->left){
                ret += "," + to_string(cur->left->val);
                q.push(cur->left);
            }
            else
                ret += ",null";
            
            if(cur->right){
                ret += "," + to_string(cur->right->val);
                q.push(cur->right);
            }
            else
                ret += ",null";
        }
        return ret + "]";
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data){
        vector<string> vec = get_vector(data);

        if(vec.size()==0 || (vec.size() == 1 && vec[0]=="nulll"))
            return NULL;

        TreeNode* root = new TreeNode(atoi(vec[0].c_str()));
        queue<TreeNode*> q;
        q.push(root);
        int index = 1;
        while(!q.empty()){
            TreeNode* cur = q.front();
            q.pop();

            assert(vec.size() - index >= 2);
            if(vec[index] != "null"){
                cur->left = new TreeNode(atoi(vec[index].c_str()));
                q.push(cur->left);
            }
            index ++;

            if(vec[index] != "null"){
                cur->right = new TreeNode(atoi(vec[index].c_str()));
                q.push(cur->right);
            }
            index ++;
        }
        return root;
    }
private: 
    vector<string> get_vector(const string& data){
        string s = data.substr(1, data.size() -2) + ",";

        vector<string> res;
        int i = 0;
        while(i < s.size()){
            int comma = s.find(',', i);
            res.push_back(s.substr(i, comma - i));
            i = comma + 1;
        }
        return res;
    }
};

int main() {

    TreeNode* root = new TreeNode(1);
    root->left = new TreeNode(2);
    root->right = new TreeNode(3);
    root->right->left = new TreeNode(4);
    root->right->right = new TreeNode(5);

    string s = Codec().serialize(root);
    cout << s << endl;

    TreeNode* x = Codec().deserialize(s);
    cout << Codec().serialize(x) << endl;

    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM