【LeetCode 572】另一棵树的子树:当"递归"遇上"递归"
在二叉树的问题中,有一类题目非常像"大海捞针"。题目给定两棵树 root(大树)和 subRoot(小树),问你:小树是不是大树的一部分?
这道题(LeetCode 572)其实是 LeetCode 100. 相同的树 的进阶版。如果你已经掌握了如何判断两棵树相同,那么解决这道题只需要在外面再套一层逻辑即可。
今天我们就来拆解这个"树中找树"的经典解法。
1. 核心解题思路:暴力匹配
要在 root 中找到 subRoot,逻辑其实非常直白:
拿着 subRoot 这张"模具",去 root 的每一个节点比对一下:
- 当前节点 :
root这棵树本身,和subRoot长得一样吗? - 左子树 :如果不一样,那去
root的左子树 里找找,有没有和subRoot一样的? - 右子树 :如果还没找到,去
root的右子树里找找?
只要以上三个地方有一个匹配成功,结果就是 true。
这就引出了我们的两个核心函数:
isSameTree:负责"判断两棵树是否完全相同"(即 LeetCode 100 的逻辑)。isSubtree:负责"遍历大树的每一个节点",并调用上面的函数。
2. 代码逻辑深度拆解
我们将代码分为两个部分来看,这样逻辑会非常清晰。
第一部分:判断"相同的树" (Helper Function)
这是判断的基石。给你两个根节点 p 和 q,怎么判断它们代表的树是一模一样的?
java
public boolean isSameTree(TreeNode p, TreeNode q) {
// 1. 两个都为空,认为是相同的 -> True
if(p == null && q == null) { return true; }
// 2. 一个为空,一个不为空,肯定不同 -> False
if(p == null && q != null) { return false; }
if(p != null && q == null) { return false; }
// 3. 都不为空,但值不一样 -> False
if(p.val != q.val) { return false; }
// 4. 当前节点值一样,必须左边相同 AND 右边也相同 -> 递归
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
- 同步递归 :这里的关键是
&&。两棵树要相同,必须是根节点值相同 ,且左子树相同 ,且右子树相同。缺一不可。
第二部分:在主树中"寻觅" (Main Logic)
有了上面的判断工具,主函数的任务就是遍历 root 的每一个节点,把它当作潜在的起点。
java
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
// 1. 边界处理:
// 如果两棵树都空了,视为匹配成功
if(root == null && subRoot == null) return true;
// 如果大树空了(没路了),但小树还有,说明没找到 -> False
if(root == null && subRoot != null) return false;
// 如果大树还有,小树空了,题目通常定义空树是任何树的子树,
// 但根据题目约束通常 subRoot 不为空,这里你的逻辑处理也没问题。
if(root != null && subRoot == null) return false;
// 2. 核心判断(三选一):
// 情况 A:就在当前这个位置,两棵树完全一样!
if (isSameTree(root, subRoot)) return true;
// 情况 B:当前位置不一样,那我去左子树里找找看?
if (isSubtree(root.left, subRoot)) return true;
// 情况 C:左边也没找到,那我去右子树里找找看?
if (isSubtree(root.right, subRoot)) return true;
// 3. 找遍了左右都没找到 -> False
return false;
}
- 或运算 (
||) 的逻辑 :这里用的是多次if ... return true,本质上就是逻辑或。- 我是子树?
- 或者,我的左孩子包含子树?
- 或者,我的右孩子包含子树?
只要满足其一,整棵树就包含子树。
3. 为什么代码中要写两个 if(p==null ...)?
在 isSameTree 和 isSubtree 中,都对 null 进行了详细的判断:
java
if(p == null && q == null) return true;
if(p == null && q != null) return false;
if(p != null && q == null) return false;
这其实是递归的终止条件(Base Case)。
- 完全匹配 :必须两个指针同时走到
null,才说明之前的路径结构完全一致。 - 结构不匹配:如果一个树走完了(null),另一个树还有节点(not null),说明结构不对等,直接判错。
4. 复杂度分析
假设 root 的节点数为 NNN,subRoot 的节点数为 MMM。
- 时间复杂度 :O(N×M)O(N \times M)O(N×M)。
- 在最坏的情况下(例如两棵树里的值全部相同),对于
root的每一个节点,我们都要执行一次isSameTree,而isSameTree最多遍历 MMM 个节点。
- 在最坏的情况下(例如两棵树里的值全部相同),对于
- 空间复杂度 :O(max(N,M))O(\max(N, M))O(max(N,M))。
- 取决于递归调用栈的深度。
5. 总结
这道题完美展示了递归的嵌套使用:
- 外层递归(
isSubtree):负责遍历,类似于 DFS 寻找目标节点。 - 内层递归(
isSameTree):负责比对,验证结构和值是否一致。
理解了这道题,你就掌握了二叉树问题的核心思想:将大问题拆解为当前节点的判断 + 左右子树的递归处理。