算法题 另一棵树的子树

判断另一棵树的子树

LeetCode 572. 另一棵树的子树

问题描述

给你两棵二叉树 rootsubRoot。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true;否则,返回 false

二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。

示例

输入: root = [3,4,5,1,2], subRoot = [4,1,2]
输出: true

输入: root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2]
输出: false

算法思路

核心思想

  1. 双重递归
    • 外层递归:遍历主树的每个节点
    • 内层递归:判断当前节点为根的子树是否与目标子树相同
  2. 树相等判断:递归比较两棵树的结构和节点值
  3. 剪枝优化:可以先比较根节点值,不相等直接跳过

关键

  • 子树必须完全相同(结构+值),不能只是部分匹配
  • 空树:两个空树相等,一个空一个非空不相等

代码实现

方法一:双重递归

/**
 * Definition for a binary tree node.
 */
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode() {}
    TreeNode(int val) { this.val = val; }
    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

class Solution {
    /**
     * 双重递归:外层遍历主树,内层判断子树是否相等
     * 
     * @param root 主树根节点
     * @param subRoot 子树根节点
     * @return 是否包含相同子树
     */
    public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        // 空树处理
        if (root == null) {
            return subRoot == null;
        }
        
        // 如果当前节点为根的子树与subRoot相等,返回true
        if (isSameTree(root, subRoot)) {
            return true;
        }
        
        // 否则在左右子树中继续查找
        return isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot);
    }
    
    /**
     * 判断两棵树是否完全相同
     * 
     * @param p 第一棵树根节点
     * @param q 第二棵树根节点
     * @return 两棵树是否相同
     */
    private boolean isSameTree(TreeNode p, TreeNode q) {
        // 两个都为空
        if (p == null && q == null) {
            return true;
        }
        // 一个为空,一个不为空
        if (p == null || q == null) {
            return false;
        }
        // 节点值不同
        if (p.val != q.val) {
            return false;
        }
        
        // 递归比较左右子树
        return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
    }
}

方法二:序列化字符串匹配

class Solution {
    /**
     * 序列化:将树转换为字符串,用字符串匹配
     * 
     * @param root 主树根节点
     * @param subRoot 子树根节点
     * @return 是否包含相同子树
     */
    public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        if (root == null) {
            return subRoot == null;
        }
        
        String rootStr = serialize(root);
        String subRootStr = serialize(subRoot);
        
        // 检查子串
        return rootStr.contains(subRootStr);
    }
    
    /**
     * 前序遍历序列化树(包含null节点)
     * 
     * @param node 树节点
     * @return 序列化字符串
     */
    private String serialize(TreeNode node) {
        if (node == null) {
            return "null,";
        }
        
        // 前序遍历:根,左,右
        return node.val + "," + serialize(node.left) + serialize(node.right);
    }
}

算法分析

  • 时间复杂度
    • 双重递归:O(m × n) - m是主树节点数,n是子树节点数
    • 序列化:O(m + n) - 序列化O(m+n),字符串匹配O(m×n)(最坏情况)
  • 空间复杂度
    • 双重递归:O(max(h1, h2)) - h1,h2是树的高度
    • 序列化:O(m + n) - 存储序列化字符串

算法过程

输入:root = [3,4,5,1,2], subRoot = [4,1,2]

双重递归

isSubtree(3, 4):
  isSameTree(3, 4) → false (3≠4)
  isSubtree(4, 4) → true
    isSameTree(4, 4) → true
      isSameTree(1, 1) → true
        isSameTree(null, null) → true
        isSameTree(null, null) → true
      isSameTree(2, 2) → true
        isSameTree(null, null) → true
        isSameTree(null, null) → true

返回 true

序列化

root序列化: "3,4,1,null,null,2,null,null,5,null,null,"
subRoot序列化: "4,1,null,null,2,null,null,"

检查主字符串是否包含子字符串 → true

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 构建二叉树
    TreeNode buildTree(Integer[] arr) {
        if (arr.length == 0 || arr[0] == null) return null;
        
        TreeNode root = new TreeNode(arr[0]);
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        int i = 1;
        
        while (!queue.isEmpty() && i < arr.length) {
            TreeNode node = queue.poll();
            
            if (i < arr.length && arr[i] != null) {
                node.left = new TreeNode(arr[i]);
                queue.offer(node.left);
            }
            i++;
            
            if (i < arr.length && arr[i] != null) {
                node.right = new TreeNode(arr[i]);
                queue.offer(node.right);
            }
            i++;
        }
        
        return root;
    }
    
    // 测试用例1:标准示例
    TreeNode root1 = buildTree(new Integer[]{3,4,5,1,2});
    TreeNode subRoot1 = buildTree(new Integer[]{4,1,2});
    System.out.println("Test 1: " + solution.isSubtree(root1, subRoot1)); // true
    
    // 测试用例2:相似但不相同
    TreeNode root2 = buildTree(new Integer[]{3,4,5,1,2,null,null,null,null,0});
    TreeNode subRoot2 = buildTree(new Integer[]{4,1,2});
    System.out.println("Test 2: " + solution.isSubtree(root2, subRoot2)); // false
    
    // 测试用例3:相同树
    TreeNode root3 = buildTree(new Integer[]{1,1});
    TreeNode subRoot3 = buildTree(new Integer[]{1});
    System.out.println("Test 3: " + solution.isSubtree(root3, subRoot3)); // true
    
    // 测试用例4:空子树
    TreeNode root4 = buildTree(new Integer[]{1,2,3});
    System.out.println("Test 4: " + solution.isSubtree(root4, null)); // false
    System.out.println("Test 4b: " + solution.isSubtree(null, null)); // true
    
    // 测试用例5:单节点
    TreeNode root5 = new TreeNode(1);
    TreeNode subRoot5 = new TreeNode(1);
    System.out.println("Test 5: " + solution.isSubtree(root5, subRoot5)); // true
    
    // 测试用例6:数值相同但结构不同
    TreeNode root6 = buildTree(new Integer[]{1,2,3});
    TreeNode subRoot6 = buildTree(new Integer[]{1,2});
    System.out.println("Test 6: " + solution.isSubtree(root6, subRoot6)); // false
    
    // 测试用例7:边界情况 - 大树包含小子树
    TreeNode root7 = buildTree(new Integer[]{1,2,3,4,5,6,7});
    TreeNode subRoot7 = buildTree(new Integer[]{2,4,5});
    System.out.println("Test 7: " + solution.isSubtree(root7, subRoot7)); // true
    
    // 测试用例8:负数节点
    TreeNode root8 = buildTree(new Integer[]{-1,-2,-3});
    TreeNode subRoot8 = buildTree(new Integer[]{-2});
    System.out.println("Test 8: " + solution.isSubtree(root8, subRoot8)); // true
}

关键点

  1. 树相等判断

    • 必须同时比较结构和节点值
  2. 双重递归

    • 外层:在主树中寻找可能的匹配起点
    • 内层:验证以当前节点为根的子树是否完全匹配
  3. 序列化

    • 必须包含空节点信息,否则无法区分不同结构
  4. 边界情况

    • 空树处理
    • 单节点树
    • 完全相同的树
    • 结构相似但不相同的树

常见问题

  1. 为什么序列化要包含null节点?

    • 如果不包含null,无法区分 [1,2][1,null,2] 这两种不同结构
    • 包含null确保序列化结果唯一对应树的结构
  2. 双重递归的时间复杂度为什么是O(m×n)?

    • 最坏情况下,对主树的每个节点都要进行一次完整的子树比较
    • 每次比较需要O(n)时间,主树有m个节点
  3. 如何处理重复子树的情况?

    • 只需要判断是否存在,找到第一个匹配就可以返回true
    • 不需要找出所有匹配的子树
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值