《劍指offer》樹 專題

記錄《劍指offer》中所有關(guān)于樹的題目,以及LeetCode中的相似題目。

相關(guān)題目列表

index description key words done data
6 重建二叉樹 遍歷,重建 Y 18-3-7
18 樹的子結(jié)構(gòu) 遍歷,遞歸 Y 18-3-7
19 二叉樹的鏡像 遍歷,鏡像 Y 18-3-8
23 從上往下打印二叉樹 層序遍歷 Y 18-3-8
24 二叉搜索樹的后序遍歷序列 BST Y 18-3-9
25 二叉樹中和為某一值的路徑 路徑和 Y 18-3-9
27 二叉搜索樹與雙向鏈表 BST與鏈表 Y 18-3-12
39_1 二叉樹的深度 樹的深度 Y 18-3-12
39_2 判斷是否是AVL樹 深度,AVL樹 Y 18-3-13
50 樹中兩個結(jié)點的最低公共祖先 公共祖先 Y 18-3-13
58 二叉樹的下一個結(jié)點 下一結(jié)點 Y 18-3-15
59 對稱的二叉樹 遍歷,對稱 Y 18-3-15
60 把二叉樹打印成多行 層序遍歷 Y 18-3-17
61 按之字順序打印二叉樹 層序遍歷 Y 18-3-17
62 序列化二叉樹 序列化 Y 18-3-18
63 二叉搜索樹的第k個結(jié)點 BST,中序遍歷 Y 18-3-18

題目

樹是一種最常考的數(shù)據(jù)結(jié)構(gòu),尤其是二叉樹,其中二叉樹的各種遍歷方法,以及樹的各種子結(jié)構(gòu)操作,都需要靈活掌握。

面試題6: 重建二叉樹

題目: 輸入某二叉樹的前序遍歷和中序遍歷的結(jié)果,請重建出該二叉樹。假設(shè)輸入的前序遍歷和中序遍歷的結(jié)果中都不包含重復(fù)的數(shù)字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建出如下圖的二叉樹,并輸出它的頭結(jié)點。

重構(gòu)二叉樹

題目分析

根據(jù)二叉樹的前序遍歷與中序遍歷序列的特定,可以判斷節(jié)點直接的相對位置,從而得出重構(gòu)二叉樹。
具體的做法是,根據(jù)前序遍歷得知根結(jié)點,然后根據(jù)中序遍歷將序列分成左右子樹,從而遞歸完成二叉樹的重構(gòu)。

參考代碼

#include<iostream>
#include<vector>

using namespace std;


struct BinaryTreeNode
{
    int val;
    BinaryTreeNode* left;
    BinaryTreeNode* right;

    BinaryTreeNode(int x) : val(x), left(NULL), right(NULL) {}

    //輸出前序遍歷結(jié)果
    static void PreOrder(BinaryTreeNode* root)
    {
        if (root == NULL)
        {
            return;
        }
        cout << root->val << " ";
        PreOrder(root->left);
        PreOrder(root->right);
    }

    //輸出中序遍歷結(jié)果,采用static
    static void InOrder(BinaryTreeNode* root)
    {
        if (root == NULL)
        {
            return;
        }
        InOrder(root->left);
        cout << root->val << " ";
        InOrder(root->right);
    }

    //輸出后序遍歷結(jié)果
    static void LatOrder(BinaryTreeNode* root)
    {
        if (root == NULL)
        {
            return;
        }
        LatOrder(root->left);
        LatOrder(root->right);
        cout << root->val << " ";
    }
};

class Solution
{
public:
    BinaryTreeNode* reConstructBinaryTree(vector<int> pre, vector<int> in)
    {
        //前序遍歷的長度和中序遍歷相同
        if (pre.size() != in.size())
        {
            return NULL;
        }

        //長度不能為0
        int length = pre.size();
        if (length == 0)
        {
            return NULL;
        }

        //int length = pre.size();

        int value = pre[0];     //前序遍歷的第一個結(jié)點是根結(jié)點

        BinaryTreeNode *root = new BinaryTreeNode(value);

        //找到中序遍歷中的根結(jié)點
        int rootIndex = 0;
        for (int i = 0; i < length; ++i)
        {
            if (in[i] == value)
            {
                rootIndex = i;
                break;
            }
        }

        //區(qū)分左子樹和右子樹
        //中序遍歷中,根左邊的就是左子樹,右邊的就是右子樹
        //前序遍歷中,根后面的是先遍歷左子樹,然后遍歷右子樹

        //首先確定左右子數(shù)的長度,從中序遍歷in中確定

        vector<int> preLeft, inLeft, preRight, inRight;

        for (int i = 0; i < rootIndex; ++i)
        {
            //前序遍歷的第一個結(jié)點是根結(jié)點,所以是i+1
            preLeft.push_back(pre[i+1]);
            //中序遍歷的前i個結(jié)點即使中序遍歷的左子樹
            inLeft.push_back(in[i]);
        }
        for (int i = rootIndex + 1; i < length; ++i)
        {
            //前序遍歷的右子樹
            preRight.push_back(pre[i]);
            //中序遍歷的右子樹
            inRight.push_back(in[i]);
        }


        root->left = reConstructBinaryTree(preLeft, inLeft);
        root->right = reConstructBinaryTree(preRight, inRight);

        return root;
    }
};

int main()
{
    int pre[] = {1,2,4,7,3,5,6,8};
    int in[] = {4,7,2,1,5,3,8,6};

    vector<int> preOrder(pre, pre+8);
    vector<int> inOrder(in, in+8);

    Solution solu;
    BinaryTreeNode *root = solu.reConstructBinaryTree(preOrder, inOrder);

    cout << root->val << endl;

    BinaryTreeNode::PreOrder(root);
    cout << endl;
    BinaryTreeNode::InOrder(root);
    cout << endl;
    BinaryTreeNode::LatOrder(root);

    return 0;
}

相似題目

本題與LeetCode中的105. Construct Binary Tree from Preorder and Inorder Traversal
完全一致,另外LeetCode中還有一道已知中序和后續(xù)的題目106. Construct Binary Tree from Inorder and Postorder Traversal
,這兩題的參考代碼見:
LeetCode 105 code
LeetCode 106 code
還可以在牛客網(wǎng) 劍指offer上完成對本題的練習(xí)。

面試題18:樹的子結(jié)構(gòu)

題目: 輸入兩棵二叉樹A和B,判斷B是不是A的子結(jié)構(gòu)。

題目分析

要查找A中是否存在和B結(jié)構(gòu)一樣的子樹,可以分為兩步進(jìn)行:第一步在A中找到和B的根結(jié)點的值一樣的結(jié)點R,第二步判斷樹A中以R為根結(jié)點的子樹是不是包含和樹B一樣的結(jié)構(gòu)。
第一步在樹A中查找結(jié)點,這實際上就是樹的遍歷,遍歷可以采用遞歸的方式完成。
第二步是判斷A中以R為根結(jié)點的子樹是不是和B有相同的結(jié)構(gòu),也可以采用遞歸的方式完成。

參考代碼

#include<iostream>
using namespace std;

struct BinaryTreeNode
{
    int val;
    BinaryTreeNode* left;
    BinaryTreeNode* right;
};

class Solution
{
public:
    //尋找到與tree2根結(jié)點相同的結(jié)點。才執(zhí)行之后的操作,若是找不到則向下遍歷,知道找到再判斷左右子樹。
    bool HasSubtree(BinaryTreeNode* pRoot1, BinaryTreeNode* pRoot2)
    {
        bool result = false;
        if (pRoots != NULL && pRoot2 != NULL)   //兩棵樹不能為空
        {
            if (pRoot1->val == pRoot2->val)     //根結(jié)點相等之后,轉(zhuǎn)為比較左右子結(jié)點
                result = DoesTreeHaveTree2(pRoot1, pRoot2);
            if (!result)        //左右子節(jié)點不滿足條件,在tree1中重新尋找與tree2根結(jié)點相等的結(jié)點
                result = HasSubtree(pRoot1->left, pRoot2);  //在左子樹中找
            if (!result)
                result = HasSubtree(pRoot1->right, pRoot2); //在右子樹中找
        }
        return result;
    }
private:
    bool DoesTreeHaveTree2(BinaryTreeNode* pRoot1, BinaryTreeNode* pRoot2)
    {
        if (pRoot2 == NULL)     //經(jīng)過查找對比,最好到了tree2的葉子節(jié)點,則遍歷結(jié)束。返回true,這個條件必須在下一個條件之前
            return true;
        if (pRoot1 = NULL)      //若查找對比最后到了tree1的葉子節(jié)點則,證明沒有找到對應(yīng)的子樹
            return false;

        if (pRoot1->val != pRoot2->val)
            return false;

        //如果根結(jié)點相等,則分別判斷左右子樹
        return DoesTreeHaveTree2(pRoot1->left, pRoot2->left) && DoesTreeHaveTree2(pRoot1->right, pRoot2->right);
    }
};

相似題目

本題與LeetCode中的572. Subtree of Another Tree完全一致,參考代碼見:
LeetCode 572 code
還可以在牛客網(wǎng) 劍指offer上完成對本題的練習(xí)。

面試題19: 二叉樹的鏡像

題目: 請完成一個函數(shù),輸入一個二叉樹,該函數(shù)輸出它的鏡像。

題目分析

分析題目可知,二叉樹的鏡像問題,其實就是遞歸交換根結(jié)點的左右子樹的問題。
于是,我們通過先序遍歷這棵樹的每個結(jié)點,如果該結(jié)點存在你左右子樹,就交換它的兩個子結(jié)點,當(dāng)交換玩所有非葉子節(jié)點的左右子結(jié)點后,就得到了二叉樹的鏡像。

參考代碼

#include<iostream>

using namespace std;

struct BinaryTreeNode
{
    int val;
    BinaryTreeNode* left;
    BinaryTreeNode* right;

    BinaryTreeNode(int x): val(x), left(NULL), right(NULL) {}

    static void PreOrder(BinaryTreeNode* root)
    {
        if (root == NULL)
            return;
        cout << root->val << " ";
        PreOrder(root->left);
        PreOrder(root->right);
    }

    static void InOrder(BinaryTreeNode* root)
    {
        if (root == NULL)
            return;
        InOrder(root->left);
        cout << root->val << " ";
        InOrder(root->right);
    }

    static void LatOrder(BinaryTreeNode* root)
    {
        if (root == NULL)
            return;
        LatOrder(root->left);
        cout << root->val << " ";
        LatOrder(root->right);
    }
};

class Solution
{
public:
    void mirrorOfBinaryTree(BinaryTreeNode* pNode)
    {
        if (pNode == NULL)
            return NULL;
        if (pNode->left == NULL && pNode->right == NULL)    //μY1é?áê?ì??t
            return NULL;

        BinaryTreeNode* temp;
        temp = pNode->left;
        pNode->left = pNode->right;
        pNode->right = temp;

        if (pNode->left)
            mirrorOfBinaryTree(pNode->left);
        if (pNode->right)
            mirrorOfBinaryTree(pNode->right);
    }
};

相似題目

本題與LeetCode中的226. Invert Binary Tree一題完全一致,參考代碼見:
LeetCode 226 code
還可以在牛客網(wǎng) 劍指offer上完成對本題的練習(xí)。

面試題23: 從上往下打印二叉樹

題目: 從上往下打印二叉樹的每個結(jié)點,同一層的結(jié)點按照從左往右的順序打印。

題目分析

本題是二叉樹的層序遍歷,使用雙向隊列完成。

參考代碼

//二叉樹的層序遍歷
/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/
class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {
        vector<int> result;

        if (root == NULL)
            return result;

        std::deque<TreeNode*> dequeTreeNode;    //利用隊列先入先出的特性
        dequeTreeNode.push_back(root);
        while (dequeTreeNode.size()){   //如果隊列中存在元素,在彈出首元素的同時壓入這個元素對應(yīng)的左右子樹
            TreeNode* pNode = dequeTreeNode.front();
            result.push_back(pNode->val);
            dequeTreeNode.pop_front();

            if (pNode->left != NULL)
                dequeTreeNode.push_back(pNode->left);
            if (pNode->right != NULL)
                dequeTreeNode.push_back(pNode->right);
        }
        return result;

    }
};

相似題目

本題與LeetCode中的102. Binary Tree Level Order Traversal
完全一致,另外LeetCode中還有一道本題的延伸107. Binary Tree Level Order Traversal II
。這兩題的參考代碼見:
LeetCode 102 code
LeetCode 107 code
還可以在牛客網(wǎng) 劍指offer上完成對本題的練習(xí)。

面試題24: 二叉搜索樹的后續(xù)遍歷序列

題目: 輸入一個整數(shù)數(shù)組,判斷該數(shù)組是不是某二叉搜索樹的后序遍歷結(jié)果。如果是返回true,否則返回false。假設(shè)輸入的數(shù)組的任意兩個數(shù)字都互不相同。

題目分析

首先,二叉搜索樹要求每個結(jié)點左子樹的值都小于本結(jié)點的值,所有右子樹上的值都大于本節(jié)點的值。
以數(shù)組{5,7,6,9,11,10,8}為例,后序遍歷中8為樹的根結(jié)點,所以5,7,6為8的左子樹,9,11,10為8的右子樹,因此在8結(jié)點上滿足條件。由此可以用相同的方法判斷每個非葉子結(jié)點是否都滿足二叉搜索樹的條件。

參考代碼

class Solution {
public:
    bool VerifySquenceOfBST(vector<int> sequence) {
        int length = sequence.size();
        if (length == 0)
            return false;

        return result(sequence, 0, length - 1);
    }

private:
    bool result(vector<int> sequence, int start, int end){
        if (start >= end)
            return true;    //遞歸結(jié)束條件

        int root = sequence[end];
        int i = start;
        while (sequence[i] < root){
            ++i;
        }

        //判斷右子樹
        int j = i;
        while (j < end){
            if (sequence[j] < root){
                return false;
            }
            ++j;
        }

        return result(sequence, start, i-1) && result(sequence, i, end-1);
    }
};

相似題目

LeetCode中有一道是驗證二叉搜索樹的前序序列,但是是一道收費題255
Verify Preorder Sequence in Binary Search Tree
,其實方法都是一樣的。
可以在牛客網(wǎng) 劍指offer上對本題進(jìn)行練習(xí)。

面試題25: 二叉樹中和為某一值的路徑

題目: 輸入一棵二叉樹和一個整數(shù),打印出二叉樹中結(jié)點值的和為輸入整數(shù)的所有路徑。從樹的根結(jié)點開始往下一直到葉結(jié)點所經(jīng)過的結(jié)點形成一條路徑。

題目分析

此題用前序遍歷的方式訪問到某一結(jié)點時,我們把該結(jié)點添加到路徑上,并累加該結(jié)點的值。如果該結(jié)點為葉結(jié)點并且路徑中結(jié)點值的和正好等于輸入整數(shù),則當(dāng)前路徑符合,打印出來。如果當(dāng)前結(jié)點不是葉結(jié)點,則繼續(xù)訪問它的子結(jié)點。當(dāng)前結(jié)點結(jié)束訪問后,遞歸函數(shù)將自動回到它的父結(jié)點。因此我們在函數(shù)退出之前要在路徑上刪除當(dāng)前結(jié)點并減去當(dāng)前結(jié)點的值,以確保返回父結(jié)點時路徑剛好是從根結(jié)點到父結(jié)點的路徑。我們不難看出,這其實就是一個棧,因為路徑要與遞歸調(diào)用狀態(tài)一致,而遞歸調(diào)用的本質(zhì)就是一個壓棧和出棧的過程。

參考代碼

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/
class Solution {
public:
    vector<vector<int>> res;    //將res和path設(shè)置為全局變量
    vector<int> path;
    vector<vector<int>> FindPath(TreeNode* root,int expectNumber) {
        find(root, expectNumber);
        return res;
    }
private:
    void find(TreeNode* root, int sum){
        if (root == NULL)
            return;
        path.push_back(root->val);
        bool isLeaf = root->left == NULL && root->right == NULL;
        if ((root->val == sum) && isLeaf){      //判斷是否滿足條件
            res.push_back(path);
        }
        else{       //如果不滿足,則遞歸
            if (root->left != NULL)
                find(root->left, sum - root->val);
            if (root->right != NULL)
                find(root->right, sum - root->val);
        }
        path.pop_back();    //在返回到父結(jié)點之前,在路徑上刪除當(dāng)前節(jié)點
    }
};

相似題目

本題與LeetCode中的113. Path Sum II完全一致,另外LeetCode中還有兩道類似題目,分別是本題的簡化版本(判斷是否存在一個路徑)112. Path Sum; 以及強化版本(不設(shè)定路徑起始限制)437. Path Sum III
這三道題的參考代碼見:
LeetCode 113 code
LeetCode 112 code
LeetCode 437 code

還可以在牛客網(wǎng) 劍指offer上完成對本題的練習(xí)。

面試題27: 二叉樹與雙向鏈表

題目: 輸入一棵二叉搜索樹,將該二叉搜索樹轉(zhuǎn)換成一個排序的雙向鏈表。要求不能創(chuàng)建任何新的結(jié)點,只能調(diào)整樹中結(jié)點指針的指向。

題目分析

根據(jù)BST與排序雙向鏈表的關(guān)系,原先指向左子結(jié)點的指針調(diào)整為鏈表中指向前一個結(jié)點的指針,原先指向右子結(jié)點的指針調(diào)整為鏈表中指向后一個結(jié)點的指針。

接下來我們考慮如何進(jìn)行轉(zhuǎn)換。

根據(jù)BST中序遍歷有序的特點,我們采用中序遍歷算法從小到大遍歷二叉樹的每一個結(jié)點。當(dāng)遍歷到根結(jié)點時,我們把樹看成3部分(如下圖所示),值為10的結(jié)點、根結(jié)點值為6的左子樹,根結(jié)點值為14的右子樹。根據(jù)排序鏈表的定義,值為10的節(jié)點將和它的左子樹的最大一個節(jié)點相連,同時與右子樹中最小節(jié)點相連。

根據(jù)中序遍歷的順序,當(dāng)我們遍歷轉(zhuǎn)換到根結(jié)點時,它的左子樹已經(jīng)轉(zhuǎn)換成一個排序鏈表了,并且最后一個節(jié)點即為其中最大的結(jié)點。我們把8與10相連即可。接著遍歷轉(zhuǎn)換右子樹,并把根結(jié)點和右子樹中最小的結(jié)點相連。

參考代碼

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/
class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        TreeNode* pLastNodeInList = NULL;
        ConverNode(pRootOfTree, &pLastNodeInList);  //因為函數(shù)形參是二重指針,所以需要用取地址符

        TreeNode* pHeadOfList = pLastNodeInList;
        while (pHeadOfList != NULL && pHeadOfList->left != NULL)
            pHeadOfList = pHeadOfList->left;
        return pHeadOfList;
    }
private:
    //類似于中序遍歷
    void ConverNode(TreeNode* pNode, TreeNode** pLastNodeInList){   //因為需要對pLastNodeInList進(jìn)行動態(tài)改變,所以需要用二重指針
        if (pNode == NULL)
            return;

        TreeNode* pCurrent = pNode;
        if (pCurrent->left != NULL)
            ConverNode(pCurrent->left, pLastNodeInList);

        pCurrent->left = *pLastNodeInList;

        if (*pLastNodeInList != NULL)
            (*pLastNodeInList)->right = pCurrent;

        *pLastNodeInList = pCurrent;

        if (pCurrent->right != NULL)
            ConverNode(pCurrent->right, pLastNodeInList);
    }
};

相似題目

本題與LeetCode中的109. Convert Sorted List to Binary Search Tree類似,其是將單鏈表轉(zhuǎn)化為BST。
還可以在牛客網(wǎng) 劍指offer上完成對本題的練習(xí)。

面試題39_1: 二叉樹的深度

題目: 輸入一棵二叉樹的根結(jié)點,求該樹的深度。從根結(jié)點到葉結(jié)點一次經(jīng)過的節(jié)點形成樹的一條路徑,最長路徑的長度為樹的深度。

題目分析

首先我們可以采用面試題25中的方法尋找路徑,即可得到深度。
這里我們采用一種新的方法。
如果一棵二叉樹只有一個根結(jié)點,則它的深度為1,如果只有右子樹而沒有左子樹,則其深度就是1+右子樹的深度,以此類推,從而形成一個遞歸。

參考代碼

#include<iostream>

using namespace std;

struct TreeNode
{
    int val;
    TreeNode* left;
    TreeNode* right;
};

class Solution
{
public:
    int TreeDepth(TreeNode* pRoot)
    {
        if (pRoot == NULL)
            return 0;

        int Left = TreeDepth(pRoot->left);
        int Right = TreeDepth(pRoot->right);

        return 1+(Left >= Right) ? Left : Right;
    }
};

相似題目

本題與LeetCode中的104. Maximum Depth of Binary Tree111. Minimum Depth of Binary Tree類似,其分別是求二叉樹的最大最小深度。參考代碼見:
LeetCode 104 code
LeetCode 111 code
還可以在牛客網(wǎng) 劍指offer上完成對本題的練習(xí)。

題目39_2: 判斷是否為AVL樹

題目: 輸入一棵二叉樹的根結(jié)點,判斷該樹是不是平衡二叉樹。如果某二叉樹中任意節(jié)點的左右子樹的深度相差不超過1,那么它就是一棵平衡二叉樹。

題目分析

可以根據(jù)39_1中二叉樹的深度逐結(jié)點判斷左右子樹深度差,從而確定是否是AVL樹,但是這樣做的缺點是顯而易見的,每個節(jié)點需要訪問多次。
下面采用一種新的方法:
如果我們采用后序遍歷的方式遍歷二叉樹的每一個結(jié)點,在遍歷到一個結(jié)點之前,我們就已經(jīng)遍歷了它的左右子樹。只要在遍歷每個結(jié)點的時候記錄它的深度,我們就可以一邊遍歷一邊判斷每個節(jié)點是不是平衡的。

參考代碼

struct TreeNode
{
    int val;
    TreeNode* left;
    TreeNode* right;
};

/*===============方法一,需要多次遍歷同一節(jié)點====================*/
class Solution
{
public:
    bool IsBalancedTree(TreeNode* pRoot)
    {
        if (pRoot == NULL)
            return true;
        int Left = TreeDepth(pRoot->left);
        int Right = TreeDepth(pRoot->right);
        int diff = Left - Right;
        if (diff > 1 || diff < -1)
            return false;

        return IsBalancedTree(pRoot->left) && IsBalancedTree(pRoot->right);
    }
private:
    int TreeDepth(TreeNode* pRoot)
    {
        if (pRoot == NULL)
            return 0;
        int Left = TreeDepth(pRoot->left);
        int Right = TreeDepth(pRoot->right);

        return (Left >= Right) ? (Left + 1) : (Right + 1);  //更新深度
    }
};

/*====================方法二,每個節(jié)點只需要遍歷一次======================*/
class Solution2
{
public:
    bool IsBalancedTree(TreeNode* pRoot)
    {
        int Depth = 0;
        return IsBalanced(pRoot, Depth);
    }
private:
    bool IsBalanced(TreeNode* pRoot, int& depth)    //必須將depth設(shè)置為引用,因為在遍歷過程中depth需要改變
    {
        if (pRoot == NULL)  //遞歸結(jié)束條件
        {
            depth = 0;
            return true;
        }
        int left = right = 0;
        if (IsBalanced(pRoot->left, left) && IsBalanced(pRoot->right, right))
        {
            int diff = left - right;
            if (diff <= 1 && diff >= -1)
            {
                depth = 1 + (left>right?left:right);    //更新depth
                return true;
            }
        }
        return false;
    }
};

相似題目

本題與LeetCode中的110. Balanced Binary Tree
完全一致。參考代碼見:
LeetCode 110 code
還可以在牛客網(wǎng) 劍指offer上完成對本題的練習(xí)。

面試題50: 樹中兩個結(jié)點的最低公共祖先

題目: 首先如果樹是二叉樹,或者是BST這就是不同的題目,首先如果是BST,那由于BST的排序性,只需要將這兩個結(jié)點與根結(jié)點對比,判斷兩個節(jié)點是在左右子樹中,如果這兩個節(jié)點分別在左右子樹中,則根結(jié)點即為所求。依次類推,只需要從上到下找到這兩個結(jié)點之間的第一個結(jié)點即為所求。
如果是一棵普通二叉樹,本題最好的做法是通過兩個輔助鏈表記錄到兩個結(jié)點的路徑,從而將其轉(zhuǎn)化為求兩個鏈表的最后公共結(jié)點。

參考代碼

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

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root == NULL || p == NULL || q == NULL)
            return NULL;

        list<TreeNode*> path1;
        list<TreeNode*> path2;

        GetNodePath(root, p, path1);    //待會判斷真假
        GetNodePath(root, q, path2);

        return GetLastCommonNode(path1, path2);

    }
private:
    bool GetNodePath(TreeNode* root, TreeNode* pNode, list<TreeNode*> &path){   //得到節(jié)點路徑
        path.push_back(root);
        if (root == pNode)
            return true;

        bool found = false;

        if (!found && root->left != NULL)
            found = GetNodePath(root->left, pNode, path);
        if (!found && root->right != NULL)
            found = GetNodePath(root->right, pNode, path);

        if (!found)
            path.pop_back();

        return found;
    }

    TreeNode* GetLastCommonNode(list<TreeNode*> path1, list<TreeNode*> path2){  //尋找兩跳路徑上的最后一個公共節(jié)點
        list<TreeNode*>::iterator iterator1 = path1.begin();
        list<TreeNode*>::iterator iterator2 = path2.begin();

        TreeNode* pLast = NULL;
        while (iterator1 != path1.end() && iterator2 != path2.end()){
            if (*iterator1 == *iterator2)
                pLast = *iterator1;
            iterator1++;
            iterator2++;
        }
        return pLast;
    }
};

相似題目

LeetCode中235. Lowest Common Ancestor of a Binary Search Tree
一題為找到BST兩個結(jié)點的最低公共結(jié)點,236. Lowest Common Ancestor of a Binary Tree
為二叉樹尋找公共結(jié)點。這兩題的參考代碼見:
LeetCode 235 code
LeetCode 236 code

面試題58: 二叉樹的下一個結(jié)點

題目: 給定一個二叉樹和其中一個結(jié)點,如何找出中序遍歷順序的下一個結(jié)點?樹中的結(jié)點除了有兩個分別指向左右子結(jié)點的指針以外,還有一個指向父結(jié)點的指針。

題目分析

根據(jù)二叉樹的結(jié)構(gòu),本題中所給節(jié)點有如下幾種情況:
1、如果一個節(jié)點有右子樹,則其下一個結(jié)點就是它右子樹中最左子結(jié)點。
2、如果沒有右子樹:
2.1 如果結(jié)點就是它父結(jié)點的左子結(jié)點,則它的下一個結(jié)點就是它的父結(jié)點。
2.2 如果一個節(jié)點既沒有右子樹,并且還是它父結(jié)點的右子結(jié)點,這種情形比較復(fù)雜。我們可以沿著指向父結(jié)點的指針一直向上遍歷,直到找到一個是它父結(jié)點的左子結(jié)點的節(jié)點,這個結(jié)點如果存在,則它的父結(jié)點就是我們要找的下一個結(jié)點。

參考代碼

/*
struct TreeLinkNode {
    int val;
    struct TreeLinkNode *left;
    struct TreeLinkNode *right;
    struct TreeLinkNode *next;
    TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) {
    }
};
*/
class Solution {
public:
    TreeLinkNode* GetNext(TreeLinkNode* pNode)
    {
        if (pNode == NULL)
            return NULL;
        TreeLinkNode* pRes = NULL;
        if (pNode->right != NULL){
            TreeLinkNode* pRight = pNode->right;
            while (pRight->left != NULL)
                pRight = pRight->left;
            pRes = pRight;
        }
        else{
            while (pNode->next != NULL){    //直到找到此結(jié)點是其父結(jié)點的左孩子,如果是左子樹,則直接返回,如果不是則向上找
                if (pNode->next->left == pNode)
                    return pNode->next;
                pNode = pNode->next;
            }
            pRes = pNode->next;
        }
        return pRes;
    }
};

相似題目

可以在牛客網(wǎng) 劍指offer上完成對本題的練習(xí)。

面試題59: 對稱的二叉樹

題目: 請實現(xiàn)一個函數(shù),用來判斷一棵二叉樹是不是對稱的。

題目分析

可以通過這個二叉樹的前序遍歷與對稱前序遍歷是否相同來判斷是否是對稱二叉樹。

參考代碼

struct TreeNode
{
    int val;
    struct TreeNode* right;
    struct TreeNode* left;
};

class Solution
{
public:
    bool isSymmetrical(TreeNode* pRoot)
    {
        return isSymmetrical(pRoot, pRoot);
    }
private:
    bool isSymmetrical(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        if (pRoot1 == NULL && pRoot2 == NULL)   //兩個同時為NULL
            return true;
        if (pRoot1 == NULL || pRoot2 == NULL)   //如果只有一個先到達(dá)NULL
            return false;
        if (pRoot1->val != pRoot2->val)     //一旦不相等
            return false;

        return isSymmetrical(pRoot1->left, pRoot2->right) && isSymmetrical(pRoot1->right, pRoot2->left);
    }

};

相似題目

本題與LeetCode中的101. Symmetric Tree完全一致,參考代碼見:
LeetCode 101 code
還可以在牛客網(wǎng) 劍指offer上完成對本題的練習(xí)。

面試題60:把二叉樹打印成多行

題目: 從上到下按層打印二叉樹,同一層的結(jié)點按從左到右的順序打印,每一層打印到一行。

題目分析

本題與23題層序打印類似,可以使用雙向隊列來保存將要打印的結(jié)點。

參考代碼

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    vector<vector<int>> res;
    vector<vector<int> > Print(TreeNode* pRoot) {
        if (pRoot == NULL)
            return res;
        std::queue<TreeNode*> nodes;
        nodes.push(pRoot);
        while (!nodes.empty()){
            vector<int> temp;
            int size = nodes.size();
            for (int i = 0; i < size; ++i){
                TreeNode* pNode = nodes.front();
                temp.push_back(pNode->val);
                nodes.pop();

                if (pNode->left != NULL) nodes.push(pNode->left);
                if (pNode->right != NULL) nodes.push(pNode->right);
            }
            res.push_back(temp);
        }
        return res;
    }
};

相似題目

可以在牛客網(wǎng) 劍指offer上完成對本題的練習(xí)。

面試題61: 按之字形順序打印二叉樹

題目: 請實現(xiàn)一個函數(shù),按照之字形順序打印二叉樹,即第一行按照從左到右的順序打印,第二層按照從右到左的順序打印,第三行再按照從左到右的順序打印,其他行以此類推。

題目分析

本題與上一題一樣都是二叉樹的層序遍歷的變型題,本題與傳統(tǒng)層序遍歷的區(qū)別在于,只需要維護(hù)一個變量,這個變量的作用是判斷本行是從左往右還是從右向左。

參考代碼

struct TreeNode
{
    int val;
    TreeNode* left;
    TreeNode* right;
};

class Solution
{
public:
    vector<vector<int>> res;
    vector<vector<int>> Print(TreeNode* pRoot)
    {
        if (pRoot == NULL)
            return res;
        std::queue<TreeNode*> nodes;
        nodes.push(pRoot);
        bool even = false;  //判斷奇偶層,看是否需要reverse
        while (!nodes.empty())
        {
            vector<int> temp;
            int size = nodes.size();
            for (int i = 0; i < size; ++i)
            {
                TreeNode* pNode = nodes.front();
                temp.push_back(pNode->val);
                nodes.pop();

                if (pNode->left != NULL) nodes.push(pNode->left);
                if (pNode->right != NULL) nodes.push(pNode->right);
            }
            if (even)
                std::reverse(temp.begin(), temp.end());
            even = !even;
            res.push_back(temp);
        }
        return res;
    }
};

相似題目

本題與LeetCode中103. Binary Tree Zigzag Level Order Traversal完全一致,參考代碼見:
LeetCode 103 code
還可以在牛客網(wǎng) 劍指offer上完成對本題的練習(xí)。

面試題62: 序列化二叉樹

題目: 請事先兩個函數(shù),分別用來序列化和反序列化二叉樹。

題目分析

本題采用流的方式完成對二叉樹的序列化與反序列化。

參考代碼

void Serialize(treeNode* root, ostream& stream) {
    if (root == nullptr) {
        stream << "#,";
        return;
    }
    stream << "root->val" << ",";
    Serialize(root->left, stream);
    Serialize(root->right, stream);
}

void Deserialize(treeNode** root, istream& stream) {
    int number;
    if (ReadStream(stream, &number)) {
        *root = new treeNode();
        (*root)->val = number;
        (*root)->left = nullptr;
        (*root)->right = nullptr;
        
        Deserialize( &((*root)->left), stream);
        Deserialize( &((*root)->right), stream);
    }
}

相似題目

可以在牛客網(wǎng) 劍指offer上完成對本題的練習(xí)。

面試題63: 二叉搜索樹的第k個結(jié)點

題目: 給定一棵二叉搜索樹,請找出其中的第k大的節(jié)點。

題目分析

如果以中序遍歷的方式遍歷一棵BST,則中序遍歷的順序就是遞增的,于是我們只需要用中序遍歷算法遍歷一棵BST,就很容易找出它的第k大的結(jié)點。

參考代碼

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    TreeNode* KthNode(TreeNode* pRoot, int k)
    {
        if (pRoot == NULL || k == 0)
            return NULL;
        else
            return KthNodeCore(pRoot, k);
    }
private:
    //中序遍歷的第k個結(jié)點為所求,中序遍歷的時候我們在遞歸完左子樹之后打印根結(jié)點,
    //本題不是打印,如果左子結(jié)點不是要找的結(jié)點,才會訪問根結(jié)點,所以訪問到根結(jié)點的時候要將k-1
    //因為左子結(jié)點已經(jīng)證明不是要找的結(jié)點了,排除左子結(jié)點。這個過程可以看成目標(biāo)移位的過程,
    //每經(jīng)過一個結(jié)點,k-1,知道k==1,當(dāng)前節(jié)點就是要求的結(jié)點。
    TreeNode* KthNodeCore(TreeNode* pRoot, int& k){
        TreeNode* target = NULL;
        if (pRoot->left) target = KthNodeCore(pRoot->left, k);
        if (target == NULL){
            if (k == 1)
                target = pRoot;
            k--;
        }
        if (target == NULL && pRoot->right)
            target = KthNodeCore(pRoot->right, k);

        return target;
    }

};

相似題目

本題與LeetCode中的230. Kth Smallest Element in a BST
完全一致。參考代碼見:
LeetCode 230 code
還可以在牛客網(wǎng) 劍指offer上完成對本題的練習(xí)。

【參考】
[1] 《劍指offer》
歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明出處: wenmingxing 《劍指offer》樹專題

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內(nèi)容