剑指offer (C++版)分类整理(三):二叉树类

12 篇文章 2 订阅

1.JZ4 重建二叉树

题目描述:

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

思路:
1.先求出根结点(前序序列第一个元素)。
2.将根结点带入到中序遍历序列中求出左右子树的中序遍历序列。
3.通过左右子树的中序序列元素集合带入前序遍历序列可以求出左右子树的前序序列。
4.左右子树的前序序列第一个元素分别是根结点的左右儿子
5.求出了左右子树的4种序列可以递归上述步骤

class Solution {
public:
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
        int inlen = vin.size();
        if(inlen==0)
            return NULL;
        vector<int> left_pre,right_pre,left_vin,right_vin;
        //前序遍历第一个元素即为根结点,val等于根结点的值
        TreeNode* head = new TreeNode(pre[0]);
        //寻找中序遍历中根结点的位置,存放于变量gen中
        int gen = 0;
        for(int i = 0;i<inlen;i++)
        {
            if(vin[i]==pre[0])
            {
                gen = i;  //根结点在中序数组的位置
                break;
            }
        }
        //中序遍历中,根结点左边的结点位于二叉树左面,根结点右边的结点位于二叉树右面
        for(int i = 0;i<gen;i++)
        {
            left_vin.push_back(vin[i]);
            left_pre.push_back(pre[i+1]);//前序第一个为根结点,前gen+1个为左面结点
        }
        for(int i = gen + 1;i<inlen;i++)  //i=gen+1是因为前、中序遍历数组之间的关系
        {
            right_vin.push_back(vin[i]);
            right_pre.push_back(pre[i]);//前序第一个为根结点,前gen+1个为左面结点
        }
        //递归,对其进行上述所有步骤,即再区分子树的左、右子树,直到叶结点,递归返回
        head->left = reConstructBinaryTree(left_pre,left_vin);
        head->right = reConstructBinaryTree(right_pre,right_vin);
        return head;
    }
};

2.JZ17 树的子结构

题目描述:

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

思路:递归
取母树为 A ,子树为 B ,需要判断 B 是否为 A 的子结构。
我们可以依次遍历 A 中的所有结点,对每个在 A 中遍历到的结点依次做判断,判断该结点是否可以作为子结构 B 的根结点。需要一个判断当前两棵树是否结构相同的函数。
先序遍历 A 和 B,如果 B = NULL,说明 B 已遍历完,返回 true,如果 A = NULL, B!=NULL,说明 A中结点不足以构成子结构 B,返回false如果 A->val !=B->val,不满足结点值相等条件,返回 false

class Solution {
public:
    bool Sametree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        //判断根结点相同的两棵树是否结构相同
        if(pRoot2 == NULL)  //说明pRoot2遍历完了,返回true
            return true;
        if(pRoot1 == NULL)	//pRoot1遍历完了,pRoot2还没,说明pRoot2不是pRoot1的子树
            return false;
        if(pRoot1 -> val != pRoot2 -> val)
            return false;
        //到这一步说明前面的条件都不满足
        return Sametree(pRoot1 -> left, pRoot2 -> left) && Sametree(pRoot1 -> right, pRoot2 -> right);
    }
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        if(pRoot1 == NULL||pRoot2 == NULL)
            return false;
        return Sametree(pRoot1, pRoot2) || HasSubtree(pRoot1->left, pRoot2) || HasSubtree(pRoot1->right, pRoot2);  //双重递归
    }
};

3.JZ18 二叉树的镜像

题目描述:

操作给定的二叉树,将其变换为源二叉树的镜像。

思路:直接将根结点的左右结点交换,然后递归处理

class Solution {
public:
    void Mirror(TreeNode *pRoot) {
        if(pRoot == NULL)
            return;
        TreeNode *pTmp;
        //交换当前结点左右子树
        pTmp = pRoot->left;
        pRoot->left = pRoot->right;
        pRoot->right = pTmp;
        //递归
        Mirror(pRoot->left);  //交换左子树
        Mirror(pRoot->right);	//交换右子树
    }
};

4.JZ22 从上往下打印二叉树

题目描述:

从上往下打印出二叉树的每个结点,同层结点从左至右打印。

思路:二叉树的层序遍历,使用队列容器模拟层序过程。每次把队首元素的左右子树压入队列,然后将当前结点保存到结果中,然后弹出队首元素,直到队列为空为止。

class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {
        vector<int> res;
        if(root == NULL)    return res;
        queue<TreeNode* > q;
        q.push(root);
        while(!q.empty()){
            TreeNode* p = q.front();
            q.pop();
            res.push_back(p->val);
            if(p->left != NULL)    q.push(p->left);
            if(p->right != NULL)    q.push(p->right);
        }
        return res;
    }
};

5.JZ23 二叉搜索树的后序遍历序列

题目描述:

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

思路:递归
搜索二叉树特点:左孩子 < 根结点 < 右孩子;每棵子树都是二叉搜索树。明确后序遍历的过程以及后序遍历的结果特点:发现对于每一棵子树,它的根结点总是对应该子树的后序序列的最后一个数,然后数组中小于根结点的数都应该是左子树,剩余为右子树,然后递归判断左子树数组与右子树数组。

class Solution {
public:
    bool VerifySquenceOfBST(vector<int> sequence) {
        if(sequence.empty())
            return false;
        else
            return isBST(sequence);
    }
private:
    bool isBST(vector<int> sequence)
    {
        int len = sequence.size();
        if(len < 3)
            return true;
        else
        {
            vector<int> left;
            vector<int> right;
            int root = sequence[len - 1];
            for(int i = 0;i < len - 1;i++)
            {
                if(sequence[i] < root)
                {
                    left.push_back(sequence[i]);
                }
                else
                    break;
            }
            int split = left.size();  //左右子树分割点
            if(split == len - 1)
                return true;
            else
            {
                for(int i = split;i<len-1;i++)
                {
                    if(sequence[i] > root)
                        right.push_back(sequence[i]);
                    else
                    {
                        return false;
                        break;
                    }
                }
            }
            return isBST(left) && isBST(right);
        }
    }
};

6.JZ24 二叉树中和为某一值的路径

题目描述:

输入一颗二叉树的根结点和一个整数,按字典序打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。

思路:查找路径,回溯法。
针对当前结点值,判断当前结点路径和是否等于整数且是否是叶子结点,如果是,把当前路径保存到结果中,否则就递归下去,最后弹出这个结点。

class Solution {
    vector<vector<int> >allRes;
    vector<int> tmp;
    void dfsFind(TreeNode * node , int left){
        tmp.push_back(node->val);
        if(left-node->val == 0 && !node->left && !node->right)
            allRes.push_back(tmp);
        else {
            if(node->left) dfsFind(node->left, left-node->val);
            if(node->right) dfsFind(node->right, left-node->val);
        }
        tmp.pop_back();  //
    }
public:
    vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
        if(root) dfsFind(root, expectNumber);
        return allRes;
    }
};

7.JZ26 二叉搜索树与双向链表

题目描述:

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

思路:中序遍历即返回二叉搜索树的递增排序,使用容器保存中序遍历结果,然后改变结点指针指向即可。

class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        vector<TreeNode* > arrayTree;  //储存树结点的数组
        if(pRootOfTree)  //中序遍历所有结点
        {
            Mid(pRootOfTree,arrayTree);
        }
        else
            return NULL;
        for(int i = 0; i < arrayTree.size();i++)
        {
            if(i == 0)
            {
            	//最左侧结点
                arrayTree[i]->left == NULL;
                if(i+1 <= arrayTree.size()-1)
                    arrayTree[i]->right=arrayTree[i+1];
                else    //尾结点
                    arrayTree[i]->right=NULL;
            }
            else if(i==arrayTree.size()-1)
            {
            	//最右侧结点
                arrayTree[i]->right == NULL;
                if(i-1>=0)
                    arrayTree[i]->left=arrayTree[i-1];
                else    //头结点
                    arrayTree[i]->left=NULL;
            }
            else
            {
                arrayTree[i]->left=arrayTree[i-1];
                arrayTree[i]->right=arrayTree[i+1];
            }
        }
        return arrayTree[0];  //头结点
    }
private:
    void Mid(TreeNode* pRootOfTree,vector<TreeNode* > &arrayTree)  //中序遍历
    {
        if(pRootOfTree == NULL)
            return;
        if(pRootOfTree != NULL)
        {
            Mid(pRootOfTree->left,arrayTree);
            arrayTree.push_back(pRootOfTree);
            Mid(pRootOfTree->right,arrayTree);
        }
    }
};

8.JZ38 二叉树的深度

题目描述:

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

思路:二叉树深度为:max(左子树深度,右子树深度)+ 1

class Solution {
public:
    int TreeDepth(TreeNode* pRoot)
    {
        if(!pRoot)
            return 0;
        return max(TreeDepth(pRoot->left),TreeDepth(pRoot->right))+1;
    }
};

9.JZ39 平衡二叉树

题目描述:

输入一棵二叉树,判断该二叉树是否是平衡二叉树。
在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树

思路:首先要知道平衡二叉树的定义,即任意结点的子树的深度差都小于等于1。要先求得二叉树的子树深度,判断子树的深度差是否超过1,超过1则返回false,否则递归判断左、右子树。

class Solution {
public:
    bool IsBalanced_Solution(TreeNode* pRoot) {
        //平衡二叉树的所有子树都是平衡二叉树
        if(pRoot == NULL)
            return true;
        int left_depth = getdepth(pRoot->left);
        int right_depth = getdepth(pRoot->right);
        if(left_depth>right_depth+1 || left_depth+1<right_depth)
            return false;
        else
            return IsBalanced_Solution(pRoot->left)&&IsBalanced_Solution(pRoot->right);
    }
    int getdepth(TreeNode* pRoot)
    {
        //求深度
        if(pRoot == NULL)
            return 0;
        return max(1+getdepth(pRoot->left),1+getdepth(pRoot->right));
    }
};

10.JZ57 二叉树的下一个结点

题目描述:

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

思路:中序遍历的下一节结点。中序遍历的顺序是先访问左子树,然后访问根结点,最后访问右子树。对于当前结点,若想获得当前结点的下一个结点,就需要分析当前结点。
如果当前结点的右子树不为空,则它的下一个结点就是当前右子树最左侧结点。如果没有右子树,判断当前结点是否有父结点,如果当前结点是其父结点的左孩子,则返回父结点,否则说明当前结点是父结点的右孩子,那么就需要返回父结点循环判断,直到找到是父结点左孩子的结点将其返回,或者是根结点,退出循环,最后返回空结点。

class Solution {
public:
    TreeLinkNode* GetNext(TreeLinkNode* pNode)
    {
        if(pNode == nullptr)    return nullptr;
        if(pNode->right != nullptr)
        {//有右子树
            pNode = pNode->right;
            while(pNode->left != nullptr)
            {
                pNode = pNode->left;
            }
            return pNode;
        }
        //没有右子树
        while(pNode->next != nullptr){
            if(pNode == pNode->next->left)
                return pNode->next;
            pNode = pNode->next;
        }
        return nullptr;
        }
};

11.JZ58 对称的二叉树

题目描述;

请实现一个函数,用来判断一棵二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

思路:判断二叉树是否是对称的,判断左子树和右子树是否是对称的即可。新建一个函数用于判断两棵子树是否相等,需要注意,比较的结点是对称的。

class Solution {
public:
    bool isSymmetrical(TreeNode* pRoot)
    {
        return issymmetrical(pRoot,pRoot);
    }
    bool issymmetrical(TreeNode* pRoot1,TreeNode* pRoot2)
    {
        if(!pRoot1 && !pRoot2)
            return true;
        if(!pRoot1 || !pRoot2)
            return false;
        if(pRoot1->val != pRoot2->val)
            return false;
        return issymmetrical(pRoot1->left,pRoot2->right) && issymmetrical(pRoot2->left,pRoot1->right);
    }
};

12.JZ59 按之字形顺序打印二叉树

题目描述:

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

思路:使用双栈,分别保存奇数和偶数行,第一行从左往右压入站stack1,然后将stack1栈顶元素的左子树、右子树分别压入栈中,保证右子树在上,这样才能保证奇数行的从右往左压入栈中。将奇数栈的栈顶元素压入临时容器temp,弹出栈顶元素,直到元素为空,将temp压入res中。此时偶数栈stack2中左侧结点在下,右侧结点在上,要保证下一行从左往右输出,那就要从右往左压入将stack2栈顶元素(最右侧)的右子树、左子树分别压入栈中,保证左子树在上,这样才能保证偶数行的从左往右压入栈中将偶数栈的栈顶元素压入临时容器temp,弹出栈顶元素,直到元素为空,将temp压入res中。

class Solution {
public:
    vector<vector<int> > Print(TreeNode* pRoot) {
        vector<vector<int> > res;
        if(pRoot == NULL)
            return res;
        stack<TreeNode* > stack1;  //奇数行
        stack<TreeNode* > stack2;  //偶数行
        stack1.push(pRoot);  //保证stack1不为空
        while(!stack1.empty() || !stack2.empty())
        {
            if(!stack1.empty()){
                vector<int> temp; //临时容器,保存奇数行数字
                while(!stack1.empty()){
                    TreeNode* p = stack1.top();
                    stack1.pop();
                    temp.push_back(p->val);
                    if(p->left != NULL)    stack2.push(p->left);
                    if(p->right != NULL)    stack2.push(p->right);
                }
                res.push_back(temp);
            }
            if(!stack2.empty()){
                vector<int> temp; //临时容器,保存奇数行数字
                while(!stack2.empty()){
                    TreeNode* p = stack2.top();
                    stack2.pop();
                    temp.push_back(p->val);
                    if(p->right != NULL)    stack1.push(p->right);
                    if(p->left != NULL)    stack1.push(p->left);
                    
                }
                res.push_back(temp);
            }
        }
        return res;
    }
};

13.JZ60 把二叉树打印成多行

题目描述:

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

思路:本题与第四题层序遍历二叉树的唯一区别是每层打印一行,只需要在层序遍历的基础上维护一个变量,表示队列当前剩余的结点数,即每层的结点数,然后根据结点数将对应的每行结点保存到结果中。

class Solution {
public:
        vector<vector<int> > Print(TreeNode* pRoot) {
            vector<vector<int> > result;
            if(pRoot == NULL)
                return result;
            queue<TreeNode*> q;
            q.push(pRoot);  //保证q不为空
            while(!q.empty())
            {
                int size = q.size();  //读取每一层元素数量,这里保障了换行
                vector<int> levelelem;
                while(size--)
                {
                    TreeNode* t = q.front();
                    q.pop();
                    levelelem.push_back(t->val);
                    if(t->left != NULL) 
                        q.push(t->left);
                    if(t->right != NULL) 
                        q.push(t->right);
                }
                result.push_back(levelelem);
            }
            return result;
        }
};

14.JZ61 序列化二叉树

题目描述:

请实现两个函数,分别用来序列化和反序列化二叉树

二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空结点(#),以 ! 表示一个结点值的结束(value!)。

二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。

例如,我们可以把一个只有根结点为1的二叉树序列化为"1,",然后通过自己的函数来解析回这个二叉树

思路:题目说的比较清楚,将二叉树序列化为字符串,然后将字符串反序列化为二叉树。使用前序遍历二叉树,将结点转为字符串,然后根据字符串转为二叉树,使用递归。

class Solution {
public:
    string s;
    int j = 0;
    void Front(TreeNode *pNode){
    	//前序遍历,将二叉树存入字符串
        if(pNode == NULL){
            s += "#!";
            return;
        }
        s += to_string(pNode->val);
        s += '!';
        Front(pNode->left);
        Front(pNode->right);
    }
    char* Serialize(TreeNode *root) {    
        Front(root);
        return (char* )s.data();
    }
    TreeNode* Deserialize(char *str) {
        s = str;
        return Deserial();
    }
    
    TreeNode* Deserial(){
    	//字符串反序列化为二叉树
        if(s.size() == 0)    return NULL;
        if(s[j] == '!'){
            j++;
            if(j>s.size())
                return NULL;
        }
        if(s[j] == '#'){
            j++;
            return NULL;
        }
        int num = 0;
        while(s[j]>='0'&&s[j]<='9'){
            num = num*10 + s[j++] - '0';
        }
        TreeNode* p = new TreeNode(num);
        p->left = Deserial();
        p->right = Deserial();
        return p;
    }
};

15.JZ62 二叉搜索树的第k个结点

题目描述:

给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。

思路一:二叉搜索树中序遍历结果从小到大排列。找出第k大的结点,可以将搜索二叉树的值存入数组中,然后输出第k个元素即可。

class Solution {
public:
    vector<TreeNode*> res;
    TreeNode* KthNode(TreeNode* pRoot, int k)
    {
        if(pRoot == NULL)    return pRoot;
        midDFS(pRoot);
        if(k > res.size() || k <= 0)    return NULL;
        return res[k-1];
    }
    void midDFS(TreeNode* pRoot){
        if(pRoot == NULL)    return;
        midDFS(pRoot->left);
        res.push_back(pRoot);
        midDFS(pRoot->right);
        return;
    }
};

思路二:使用栈模拟二叉搜索树中序遍历,然后输出第k小元素即可。

class Solution {
public:
    TreeNode* KthNode(TreeNode* pRoot, int k)
    {
        if(k <= 0)    return nullptr;
        stack<TreeNode*> st;
        TreeNode* cur = pRoot;
        while(cur || st.size()) {
            while(cur) {
                st.push(cur);
                cur = cur->left;
            }
            cur = st.top();
            st.pop();
            if(-- k == 0) return cur;
            cur = cur->right;
        }
        return nullptr;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值