數據結構丨二叉樹

樹的遍歷

樹的遍歷-介紹

前序遍歷node

前序遍歷首先訪問根節點,而後遍歷左子樹,最後遍歷右子樹。ios

請看下面的例子:c++

前序

中序遍歷算法

中序遍歷是先遍歷左子樹,而後訪問根節點,而後遍歷右子樹。網絡

讓咱們一塊兒來看樹的中序遍歷:數據結構

中序

後序遍歷函數

後序遍歷是先遍歷左子樹,而後遍歷右子樹,最後訪問樹的根節點。post

咱們一塊兒來看後序遍歷的動畫演示:動畫

後序

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

另外,後序在數學表達中被普遍使用。 編寫程序來解析後綴表示法更爲容易。 這裏是一個例子:

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;
}
相關文章
相關標籤/搜索