LeetCode 572. 另一棵树的子树
问题描述
给你两棵二叉树 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
算法思路
核心思想:
- 双重递归:
- 外层递归:遍历主树的每个节点
- 内层递归:判断当前节点为根的子树是否与目标子树相同
- 树相等判断:递归比较两棵树的结构和节点值
- 剪枝优化:可以先比较根节点值,不相等直接跳过
关键:
- 子树必须完全相同(结构+值),不能只是部分匹配
- 空树:两个空树相等,一个空一个非空不相等
代码实现
方法一:双重递归
/**
* 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
}
关键点
-
树相等判断:
- 必须同时比较结构和节点值
-
双重递归:
- 外层:在主树中寻找可能的匹配起点
- 内层:验证以当前节点为根的子树是否完全匹配
-
序列化:
- 必须包含空节点信息,否则无法区分不同结构
-
边界情况:
- 空树处理
- 单节点树
- 完全相同的树
- 结构相似但不相同的树
常见问题
-
为什么序列化要包含null节点?
- 如果不包含null,无法区分
[1,2]和[1,null,2]这两种不同结构 - 包含null确保序列化结果唯一对应树的结构
- 如果不包含null,无法区分
-
双重递归的时间复杂度为什么是O(m×n)?
- 最坏情况下,对主树的每个节点都要进行一次完整的子树比较
- 每次比较需要O(n)时间,主树有m个节点
-
如何处理重复子树的情况?
- 只需要判断是否存在,找到第一个匹配就可以返回true
- 不需要找出所有匹配的子树
判断另一棵树的子树

被折叠的 条评论
为什么被折叠?



