算法专练:树

1.2236. 判断根结点是否等于子结点之和

原题链接


        给你一个 二叉树 的根结点 root,该二叉树由恰好 3 个结点组成:根结点、左子结点和右子结点。

        如果根结点值等于两个子结点值之和,返回 true ,否则返回 false 。

        示例 1:

        输入:root = [10,4,6]

        输出:true

        示例 2:

        输入:root = [5,3,1]

        输出:false

        提示:

        树只包含根结点、左子结点和右子结点

        -100 <= Node.val <= 100


        大水题,不用解释了吧。。。。

class Solution {
public:
    bool checkTree(TreeNode* root) {
        return root->val==root->left->val+root->right->val;
    }
};

2.面试题 04.10. 检查子树

原题链接


        检查子树。你有两棵非常大的二叉树:T1,有几万个节点;T2,有几万个节点。设计一个算法,判断 T2 是否为 T1 的子树。

        如果 T1 有这么一个节点 n,其子树与 T2 一模一样,则 T2 为 T1 的子树,也就是说,从节点 n 处把树砍断,得到的树与 T2 完全相同。

        注意:此题相对书上原题略有改动。

        示例1:

         输入:t1 = [1, 2, 3], t2 = [2]

        输出:true

        示例2:

         输入:t1 = [1, null, 2, 4], t2 = [3, 2]

        输出:false

        提示:

        树的节点数目范围为[0, 20000]。


        对t1,t2进行后序遍历,并计算每个结点的哈希值,最后遍历查找t1中是否有结点的哈希值是t2的哈希值就可以判断出t2是否为t1的子树。这个哈希值随便几个数算就行了,如果碰撞了就修改到不发生碰撞即可.

class Solution {
    void checkhash(TreeNode* root){
        if(!root){
            return ;
        }
        checkhash(root->left);
        checkhash(root->right);
        int l=root->left?root->left->val:19201;
        int r=root->right?root->right->val:8191;
        root->val=(root->val*3314+l*10007+r)%191201;
    }
    bool find(TreeNode* root,int val){
        if(!root){
            return false;
        }
        return root->val==val||find(root->left,val)||find(root->right,val);
    }
public:
    bool checkSubTree(TreeNode* t1, TreeNode* t2) {
        checkhash(t1);
        checkhash(t2);
        if(!t2){
            return true;
        }
        return find(t1,t2->val);
    }
};

3.1110. 删点成林

原题链接


        给出二叉树的根节点 root,树上每个节点都有一个不同的值。

        如果节点值在 to_delete 中出现,我们就把该节点从树上删去,最后得到一个森林(一些不相交的树构成的集合)。

        返回森林中的每棵树。你可以按任意顺序组织答案。

        示例 1:

        输入:root = [1,2,3,4,5,6,7], to_delete = [3,5]

        输出:[[1,2,null,4],[6],[7]]

        示例 2:

        输入:root = [1,2,4,null,3], to_delete = [3]

        输出:[[1,2,4]]

        提示:

        树中的节点数最大为 1000。

        每个节点都有一个介于 1 到 1000 之间的值,且各不相同。

        to_delete.length <= 1000

        to_delete 包含一些从 1 到 1000、各不相同的值。


        首先将to_delete中的数都映射到我们的hash数组中去,然后对树进行后序遍历。下面问题来了:

        1.如果遇到某个结点的值是to_delete中的数字我们应该怎么做?

        2.为什么要进行后序遍历,先序、中序不行吗?

        我们观察示例1这颗树:
在这里插入图片描述
在这里插入图片描述

        3,5都在树中需要我们删除,如果是先序遍历的话,先序序列是1 2 4 5 3 6 7 ,这样就很容易看出来问题了对于这棵树我们会删除3 5这两个结点。但是当删除的时候,无论是中序还是先序当删除3的时候我们永远不会再回到1了(这里可以去搜索一下在递归遍历二叉树的时候对于每个节点会回到该结点几次,什么时候回到该结点,也就是递归序),所以显然是要将某个结点的子树中全部要删除的结点都删除了之后再对该结点进行操作。这也是后序遍历的原因。

        那么对于一个要删除的结点我们要怎么操作才好?既然要将他置空了,他与父结点和子节点的链接都要断开,那么我们为了简化操作在遍历的过程中记录当前节点的父结点,和该结点是否是左结点。比如5,我们遍历到他的时候2 - >right=nullptr,由于当前结点删除之后他的子结点也不会再遇到了,我们就将其左右子树加入到答案中。对于这个例子的5来说没有能加入的子树,但是到了3的时候,6,7就要加入到答案中了。这样我们注意到了,其实并没有将要删除的结点的左右指针也断开,因为发现他是要删除的结点并进行了操作之后再也不会回到该结点了,也就没必要对他进行指针的情况操作了。

        最后我们对根结点进行判断,如果他是要被删除的结点的话我们在遍历的过程中已经将他的左右子树加入到答案中了,但是如果不是要删除的结点,最后一棵树是无法加入到答案中的,需要额外判断。

class Solution {
    int hash[1010];
    vector<TreeNode*> ans;
    void dfs(TreeNode* par,int isleft,TreeNode* root){
        if(!root){
            return ;
        }
        dfs(root,1,root->left);
        dfs(root,0,root->right);
        if(hash[root->val]){
            if(par){
                if(isleft){
                    par->left=nullptr;
                }else {
                    par->right=nullptr;
                }
            }
            if(root->left){
                ans.push_back(root->left);
            }
            if(root->right){
                ans.push_back(root->right);
            }
        }
    }
public:
    vector<TreeNode*> delNodes(TreeNode* root, vector<int>& to_delete) {
        if(!root){
            return {};
        }
        memset(hash,0,sizeof(hash));
        for(int i=0;i<to_delete.size();++i){
            hash[to_delete[i]]=1;
        }
        dfs(nullptr,0,root);
        if(!hash[root->val]){
            ans.push_back(root);
        }
        return ans;
    }
};

4.面试题 04.06. 后继者

原题链接


        设计一个算法,找出二叉搜索树中指定节点的“下一个”节点(也即中序后继)。

        如果指定节点没有对应的“下一个”节点,则返回null。

        示例 1:

        输入: root = [2,1,3], p = 1

        输出: 2

        示例 2:

        输入: root = [5,3,6,2,4,null,null,1], p = 6

        输出: null


        这道题就很简单了,题目告诉了我们要找中序遍历中某个等于p结点的下一个结点,我们设置一个变量来判断是否之前找到了p,对于中序遍历中的某个结点,先判断该结点是否是后继结点,如果之前出现了p,并且ans位空就更新ans。之后再判断他是否是p。另外题目给我们的是一个二叉搜索树,我们将其中序序列展开后放到一个数组中会发现所谓的后继节点就是数组中值为p->val的下一个元素,不过这样只是方便理解,这道题要求返回一个结点就不能这么做了。

class Solution {
    int flag;
    TreeNode* ans;
    void dfs(TreeNode* root,TreeNode* p){
        if(!root){
            return ;
        }
        dfs(root->left,p);
        if(flag&&!ans){
            ans=root;
        }
        if(root==p){
            flag=true;
        }
        dfs(root->right,p);
    }
public:
    TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) {
        flag=false;
        ans=nullptr;
        dfs(root,p);
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值