剑指Offer:树的子结构

描述
输入两棵二叉树A,B,判断B是不是A的子结构。(我们约定空树不是任意一个树的子结构)
假如给定A为{8,8,7,9,2,#,#,#,#,4,7},B为{8,9,2},2个树的结构如下,可以看出B是A的子结构
在这里插入图片描述
数据范围:
0 <= A的节点个数 <= 10000
0 <= B的节点个数 <= 10000

我的思路

是一开始乱写,不通过再讨论。

public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        if (root1 == null || root2 == null)
            return false;
        return dfs03(root1,root2);
    }

    private boolean dfs03(TreeNode root1, TreeNode root2) {
        if (root1 == null && root2!= null)
            return false;
        if (root2 == null)
            return true;
        if (root1 == root2){
            dfs03(root1.left,root2.left);
            dfs03(root1.right,root2.right);
        }
        return false;
    }

这个代码就是没通过,我修改了整体的逻辑,将遗漏的逻辑:当root1不等于root2,root1继续遍历的写了上去,并且root1 == root2这种错误也修改了,代码如下:

public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        if (root1 == null || root2 == null)
            return false;
        return dfs03(root1,root2);
    }

    private boolean dfs03(TreeNode root1, TreeNode root2) {
        if (root1 == null && root2!= null)
            return false;
        if (root2 == null)
            return true;
        if (root1.val == root2.val){//如果他们一直相等的话,就一直遍历下去,如果左右子树都是相同的return true
            //这里第一次错的因为比较成值了
            return dfs03(root1.left,root2.left) || dfs03(root1.right,root2.right);
        }
        //如果他们等了一个然后不等了,我在想这个root2该怎么处理
        dfs03(root1.left,root2);
        dfs03(root1.right,root2);
        return false;
    }

但是这样的代码依然有错,调试的时候发现果然是我写代码的时候担心的,当root1.val == root2.val成立之后,如果第二次数值不等,root2
怎么返回到之前的数,我的做法比较蠢,就是设置一个root2的根节点的全局变量,只要第二次遍历的时候没有相等root2就返回,我不知道这个回溯怎么优雅的返回。修改代码如下:

TreeNode root;
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        this.root = root2;
        if (root1 == null || root2 == null)
            return false;
        return dfs03(root1,root2);
    }

    private boolean dfs03(TreeNode root1, TreeNode root2) {
        if (root1 == null && root2!= null)
            return false;
        if (root2 == null)
            return true;
        //如果他们等了一个然后不等了,我在想这个root2该怎么处理
        if (root1.val != root2.val)
            root2 = root;
        if (root1.val == root2.val){//如果他们一直相等的话,就一直遍历下去,如果左右子树都是相同的return true
            //这里第一次错的因为比较成值了
            return dfs03(root1.left,root2.left) || dfs03(root1.right,root2.right);
        }

        dfs03(root1.left,root2);
        dfs03(root1.right,root2);
        return false;
    }

挺费解为什么这个测试用例过不了。
在这里插入图片描述
还是递归整不明白,当有返回值的时候就乱了。哎,应该一开始就想清楚,不要边改边调试最后调出来一个用例通过的答案,这样离bugfree越来越远了,还是太懒了。主要是每次第一次写的时候都是很多低级错误,得想清楚再写。
实在调不通了,想不出来了,能力有限,我让男朋友帮我看了一下。
我先讲一下我一开始的逻辑:使root1树一直往下遍历,当遍历到root1的值等于root2的值的时候,root2也开始往下遍历,一般来讲root2树都是先遍历完的(因为root2树更小),所以如果root2树遍历完就说明可以返回true了。如果root2左右子树都为true的话就返回。但是这样就会导致如果有个root1树的左子树和root2树左子树相同,但是右子树不同,最后返回的结果为true,放一下我曾以为无懈可击的代码

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
 
    public TreeNode(int val) {
        this.val = val;
 
    }
 
}
*/
public class Solution {
    TreeNode root;
    Boolean flag = false;
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        this.root = root2;
        if (root1 == null || root2 == null)
            return false;
        dfs03(root1,root2);
        return flag;
    }
 
    private boolean dfs03(TreeNode root1, TreeNode root2) {
        if (root1 == null && root2!= null)
            return false;
        if (root1 == null && root2 == null)
            return true;//因为如果AB树都同时遍历结束,root1也会为null,这时候返回false逻辑就不对了
        //我当时这里返回false是因为我想如果遍历完整个root1万一没有一个节点和root2对应的上的话,那不就得返回false
        //但是这个逻辑其实我在最后一行已经处理了
        if (root2 == null)
            return true;
        //如果他们等了一个然后不等了,我在想这个root2该怎么处理
        if (root1.val != root2.val)
            root2 = root;
        if (root1.val == root2.val){//如果他们一直相等的话,就一直遍历下去,如果左右子树都是相同的return true
            //这里第一次错的因为比较成值了
            //应该是&&,因为向左或者向右遍历到底都得为true才可以返回
            flag = dfs03(root1.left,root2.left) && dfs03(root1.right,root2.right);
        }
        if (flag){
            return true;
        }
        dfs03(root1.left,root2);
        dfs03(root1.right,root2);
        return false;
    }
 
}

以上是错的,就是我刚才说的那种情况,所以flag不能作为全局变量,这样记录了一次值为true就返回了,但其实并没有遍历到正确的答案。以下代码就是以一个大循环一个小循环来遍历出结果的
小循环:

 Boolean flag = false;
        if (root1 == null && root2!= null)
            return false;
        if (root1 == null && root2 == null)
            return true;//因为如果AB树都同时遍历结束,root1也会为null,这时候返回false逻辑就不对了
        //我当时这里返回false是因为我想如果遍历完整个root1万一没有一个节点和root2对应的上的话,那不就得返回false
        //但是这个逻辑其实我在最后一行已经处理了
        if (root2 == null)
            return true;
        //如果他们等了一个然后不等了,我在想这个root2该怎么处理
        if (root1.val != root2.val)
            root2 = root;
        if (root1.val == root2.val){//如果他们一直相等的话,就一直遍历下去,如果左右子树都是相同的return true
            //这里第一次错的因为比较成值了
            //应该是&&,因为向左或者向右遍历到底都得为true才可以返回
            flag = dfs03(root1.left,root2.left) && dfs03(root1.right,root2.right);
        }

大循环:

 Boolean flag = false;
 判断flag是否为true的逻辑
 if (flag){
            return true;
        }
//        dfs03(root1.left,root2);
//        dfs03(root1.right,root2);
        if (dfs03(root1.left,root2) || dfs03(root1.right,root2))
            return true;
            //找root1的左右子树是否有一种条件flag为true
        return false;

最后,放一下完整的代码。

public class Solution {
    TreeNode root;

    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        this.root = root2;
        if (root1 == null || root2 == null)
            return false;

        return dfs03(root1,root2);
    }

    private boolean dfs03(TreeNode root1, TreeNode root2) {
        Boolean flag = false;
        if (root1 == null && root2!= null)
            return false;
        if (root1 == null && root2 == null)
            return true;//因为如果AB树都同时遍历结束,root1也会为null,这时候返回false逻辑就不对了
        //我当时这里返回false是因为我想如果遍历完整个root1万一没有一个节点和root2对应的上的话,那不就得返回false
        //但是这个逻辑其实我在最后一行已经处理了
        if (root2 == null)
            return true;
        //如果他们等了一个然后不等了,我在想这个root2该怎么处理
        if (root1.val != root2.val)
            root2 = root;
        if (root1.val == root2.val){//如果他们一直相等的话,就一直遍历下去,如果左右子树都是相同的return true
            //这里第一次错的因为比较成值了
            //应该是&&,因为向左或者向右遍历到底都得为true才可以返回
            flag = dfs03(root1.left,root2.left) && dfs03(root1.right,root2.right);
        }
        if (flag){
            return true;
        }
//        dfs03(root1.left,root2);
//        dfs03(root1.right,root2);
        if (dfs03(root1.left,root2) || dfs03(root1.right,root2))
            return true;
        return false;
    }

}

也放一下我觉得别人写的比我好的代码,他们的大循环和小循环是分别放在两个方法里,更直观一些。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    /*
    参考:数据结构与算法的题解比较好懂
    死死记住isSubStructure()的定义:判断B是否为A的子结构
    */
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        // 若A与B其中一个为空,立即返回false
        if(A == null || B == null) {
            return false;
        }
        // B为A的子结构有3种情况,满足任意一种即可:
        // 1.B的子结构起点为A的根节点,此时结果为recur(A,B)
        // 2.B的子结构起点隐藏在A的左子树中,而不是直接为A的根节点,此时结果为isSubStructure(A.left, B)
        // 3.B的子结构起点隐藏在A的右子树中,此时结果为isSubStructure(A.right, B)
        return recur(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B);
    }

    /*
    判断B是否为A的子结构,其中B子结构的起点为A的根节点
    */
    private boolean recur(TreeNode A, TreeNode B) {
        // 若B走完了,说明查找完毕,B为A的子结构
        if(B == null) {
            return true;
        }
        // 若B不为空并且A为空或者A与B的值不相等,直接可以判断B不是A的子结构
        if(A == null || A.val != B.val) {
            return false;
        }
        // 当A与B当前节点值相等,若要判断B为A的子结构
        // 还需要判断B的左子树是否为A左子树的子结构 && B的右子树是否为A右子树的子结构
        // 若两者都满足就说明B是A的子结构,并且该子结构以A根节点为起点
        return recur(A.left, B.left) && recur(A.right, B.right);
    }
}

我还需要继续加油!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值