问题描述
给定两棵二叉树 root 和 subRoot,检验 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
解题思路
核心思想
解决这个问题的关键在于两点:
-
如何判断两棵树完全相同
-
如何在一棵树中寻找与目标树相同的子树
我们采用递归的思想来解决:
-
首先实现一个辅助函数来判断两棵树是否完全相同
-
然后在主函数中,检查当前树是否与目标树相同,如果不同则递归检查左右子树
算法步骤
-
处理边界情况:
-
如果
subRoot为空树,根据定义空树是任何树的子树,返回true -
如果
root为空而subRoot不为空,返回false
-
-
判断当前树是否相同:
-
使用辅助函数
isSame判断以当前节点为根的树是否与subRoot完全相同 -
如果相同,直接返回
true
-
-
递归搜索左右子树:
-
如果不相同,则继续在左子树和右子树中寻找
-
只要任意一边找到就返回
true
-
代码实现
java
/**
* Definition for a binary tree node.
* public 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 {
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
// 处理边界情况
if (subRoot == null) {
return true; // 空树是任何树的子树
}
if (root == null) {
return false; // 非空子树不可能是空树的子树
}
if(isSame(root,subRoot)){
return true;
}
return isSubtree(root.left,subRoot)||isSubtree(root.right,subRoot);
}
// 辅助函数:判断两棵树是否完全相同
public boolean isSame(TreeNode root1, TreeNode root2) {
// 两个都为空
if (root1 == null && root2 == null) {
return true;
}
// 一个为空,一个不为空
if (root1 == null || root2 == null) {
return false;
}
// 值不相等
if (root1.val != root2.val) {
return false;
}
// 递归比较左右子树
return isSame(root1.left, root2.left) &&
isSame(root1.right, root2.right);
}
}
复杂度分析
时间复杂度
-
最坏情况 :O(m × n),其中 m 是
root的节点数,n 是subRoot的节点数 -
最佳情况 :O(min(m, n)),当在
root的根节点就找到匹配时 -
我们需要遍历
root中的每个节点(m 个),对于每个节点,可能需要与subRoot的所有节点进行比较(n 个)
空间复杂度
-
递归栈深度 :O(h),其中 h 是
root的高度 -
在最坏情况下(树退化为链表),空间复杂度为 O(m)
-
在平衡树情况下,空间复杂度为 O(log m)
关键点解析
-
递归思想的应用:
-
通过递归分解问题,将大问题转化为小问题
-
isSame函数递归比较两棵树的每个节点 -
isSubtree函数递归搜索可能的子树
-
-
边界条件的处理:
-
空树的处理是关键,空树是任何树的子树
-
需要考虑各种可能的 null 情况组合
-
-
短路优化:
-
使用
||运算符,一旦找到匹配就立即返回,避免不必要的搜索 -
在
isSame中,一旦发现值不相等或结构不同就立即返回
-
常见错误
-
忽略空树情况:
java// 错误示例:没有正确处理 subRoot 为空的情况 public boolean isSubtree(TreeNode root, TreeNode subRoot) { if (root == null) return false; // 缺少对 subRoot == null 的处理 } -
逻辑判断不完整:
java// 错误示例:isSame 函数中处理一个为空的情况不完整 public boolean isSame(TreeNode root1, TreeNode root2) { if (root1 == null) return root2 == null; if (root2 == null) return root1 == null; // 此时 root1 不为 null,应该返回 false }
优化思路
虽然上述解法的时间复杂度为 O(m × n),但实际中通常表现良好。如果需要进一步优化,可以考虑以下方法:
-
序列化方法:
-
将两棵树序列化为字符串
-
判断
subRoot的序列化字符串是否是root序列化字符串的子串 -
时间复杂度:O(m + n)
-
-
哈希方法:
-
为每棵子树计算哈希值
-
比较哈希值来判断子树是否相同
-
时间复杂度:O(m + n)
-
总结
判断一棵树是否是另一棵树的子树是二叉树中的经典问题,主要考察递归思想和对树结构的理解。通过分解问题为"判断两棵树相同"和"在树中搜索",我们可以清晰地解决这个问题。
掌握这种递归分解问题的思想,对于解决其他树相关问题(如对称树、平衡树等)也大有帮助。
练习建议:
-
尝试使用迭代方法实现相同的功能
-
思考如何处理重复值的情况
-
尝试实现序列化方法的优化版本