Problem: 572. 另一棵树的子树
思路
题干要求我们在给定的树中找到目标子树,那么如何能确定两棵树一模一样呢?我最先想到的是3种遍历,如果两棵树的先序遍历、中序遍历一模一样,是不是就等于宣布了这两棵树一模一样?
因为我们之前也写过不少类似的题目,比如根据先序遍历、中序遍历,构建一颗二叉树。因为先序遍历和中序遍历就能完全确定一颗二叉树的结构了。
(当然,先序遍历和后续遍历二者结合也能确定一颗二叉树)
因此大致的解题思路就定下来了,我对给定树 和目标子树 都进行先序和中序的遍历,然后判断给定树的两种遍历里是否includes目标子树的两种遍历即可。
解题过程
写两个遍历的dfs,然后对两颗树用上。运行,发现用例过了,提交,不通过,为什么?
因为如果单单比较两棵树,那这样做是没问题的。
但是现在比较的是深度不相同的两棵树,从前者中找后者,这就会导致哪怕前者中的遍历结果里能includes后者,也不能保证前者中出现的所谓子树的结构和后者一模一样。
看看下面这个例子:

我们会发现
- 前者的先序遍历结果是
123,中序遍历结果是213 - 后者的先序遍历结果是
12,中序遍历结果是21
只用includes判断,明明前者树的结构比后者多1个叶子节点,includes照样会返回true,从而导致判断错误了。
既然如此,我们是不是可以再加上后序遍历,确保3种遍历都includes?
于是我们加上再试试
我们会发现
- 前者的先序遍历结果是
123,中序遍历结果是213,后序遍历结果是231 - 后者的先序遍历结果是
12,中序遍历结果是21,后序遍历结果是21
欸!后序遍历时,显然231是不能够includes 21的,因此整体结果返回false,用例正确通过。
于是我们再次提交,发现又错了,为什么?
因为子树可以只有1个节点。

这样不管怎么样,只要给定树出现过5,includes的结果就一定是true,这就又有问题了。
因此我们要对子树只有1个节点的情况做个特殊判断,避免这种情况污染返回结果即可。
最后,我们再次提交,还是错了,用例181/182,为什么?


我们会发现
- 前者的先序遍历结果是
-2 -1 0,中序遍历结果是-2 -1 0,后序遍历结果是0 -1 -2 - 后者的先序遍历结果是
-2 -1,中序遍历结果是-2 -1,后序遍历结果是-1 -2
三种顺序都能正常includes,因此返回true。
那么该怎么解决这个问题呢,用例都通过到181/182了,难不成还得推翻一切重来?
并不用,只要在遍历到null节点时,也往字符串里加个符号即可。避免左右叶子节点为空时,可能导致的遍历顺序包含情况
改好后,再提交,AC,顺便击败50%。
复杂度
- 时间复杂度: O(不知道)
- 空间复杂度: O(1)
Code
js
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @param {TreeNode} subRoot
* @return {boolean}
*/
var isSubtree = function (root, subRoot) {
let subPre = ''
let subIn = ''
let rootPre = ''
let rootIn = ''
let rootPost = ''
let subPost = ''
let isSubRootSingle = subRoot.left === null && subRoot.right === null;
if (isSubRootSingle) {
let flag = false;
const dfs = (root) => {
if (!root) {
return;
}
if (root.left === null && root.right === null) {
if (root.val === subRoot.val) {
flag = true;
}
}
dfs(root.left)
dfs(root.right)
}
dfs(root)
return flag;
}
const dfsPreorder = (root, isSub) => {
if (!root) {
if (isSub) {
subPre += 'a'
}
else {
rootPre += 'a'
}
return;
}
if (isSub) {
subPre += String(root.val)
}
else {
rootPre += String(root.val);
}
dfsPreorder(root.left, isSub)
dfsPreorder(root.right, isSub)
}
const dfsInorder = (root, isSub) => {
if (!root) {
if (isSub) {
subIn += 'a'
}
else {
rootIn += 'a'
}
return;
}
dfsInorder(root.left, isSub)
if (isSub) {
subIn += String(root.val)
}
else {
rootIn += String(root.val);
}
dfsInorder(root.right, isSub)
}
const dfsPostorder = (root, isSub) => {
if (!root) {
if (isSub) {
subPost += 'a'
}
else {
rootPost += 'a'
}
return;
}
dfsPostorder(root.left, isSub)
dfsPostorder(root.right, isSub)
if (isSub) {
subPost += String(root.val)
}
else {
rootPost += String(root.val);
}
}
dfsPreorder(root, false)
dfsPreorder(subRoot, true)
dfsInorder(root, false)
dfsInorder(subRoot, true)
dfsPostorder(root, false)
dfsPostorder(subRoot, true)
return rootIn.includes(subIn) && rootPre.includes(subPre) && rootPost.includes(subPost)
};