题目
给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s
的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。示例 1: 给定的树 s:
3 / \ 4 5 / \ 1 2
给定的树 t:
4 / \ 1 2
返回 true,因为 t 与 s 的一个子树拥有相同的结构和节点值。
示例 2: 给定的树 s:
3 / \ 4 5 / \ 1 2 / 0
给定的树 t:
4 / \ 1 2
返回 false。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/subtree-of-another-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
分析
这道题目虽然难度只有简单,但是如何达到一个比较理想的效率还是颇费一番功夫的。因为是要比较一棵树,需要保证左右子树都相等。读完题目,我初步有这么几个想法。
- 暴力搜索,只需要遍历树s中每一个节点,对每一个节点与t比较,就可以不落下地找到每一个满足条件的子树的根节点。这种方法肉眼可见得复杂,走投无路的时候才会尝试,不过我估计八成会超时,重复计算量有点大。有两个优化点:当某节点和t不相同的时候可以直接回溯,进行下一节点的比较(这个思想应该比较正常);当树的深度小于t的时候剩余部分可以直接剪枝(感觉需要标记层数,比较难以实现)。
- 利用前序遍历等方法,把树的结构扁平化。初步设想是将s和t转化成一个前序遍历的数组,然后比较两个数组。这种方法具有可用性,是因为一棵树的所有节点必然是相邻的;若存在相同和t相同的子树,那么s的数组内一定会有一个t数组。但是存在一个问题,前序遍历在s中的相邻的两个节点实际上却不一定相邻,就算数组s包含数组t也不能说明s中一定包含和t相同的子树。
- 递归。比较s和t的值,然后递归比较s左右子树和t的左右子树。也可以像暴力搜索那样剪枝。下面的第一个代码也是这个思路。
代码
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
bool compare (struct TreeNode* p1,struct TreeNode* p2){
if(!p1&&!p2) return true; //两个节点都是空的返回true
if(!p1||!p2) return false; //一空一不空返回false
if(p1->val!=p2->val) return false;
return compare(p1->left,p2->left)&&compare(p1->right,p2->right);
}
bool isSubtree(struct TreeNode* s, struct TreeNode* t){
if(t) return true;
if(!s) return false;
return compare(s,t)||isSubtree(s->left,t)||isSubtree(s->right,t);
}
运行结果
效率只能击败22.9%,看来还需要加把劲。
再分析&&代码
依照这个思路,我的代码不大幅度改动的话,应该怎么优化呢?我觉得isSubtree函数已经不能够更简单了,能减少compare函数里的语句吗?其实也不太行了,但是好像能把第二、三条if语句放进return里,具体如下:
bool compare (struct TreeNode* p1,struct TreeNode* p2)
{
if(!p1&&!p2) return true;
// if(!p1||!p2) return false;
// if(p1->val!=p2->val) return false;
return p1 && p2 && p1->val == p2->val && compare(p1->left,p2->left) && compare(p1->right,p2->right);
}
bool isSubtree(struct TreeNode* s, struct TreeNode* t){
if(!s) return false;
return compare(s,t)||isSubtree(s->left,t)||isSubtree(s->right,t);
}
再尝试运行结果
结果提升了不少,但是我感觉这可能已经是这种比较粗糙的递归方法的极限了。
为什么说粗糙呢?因为当节点p1的深度小于节点p2深度的时候,其实已经完全没有继续比较的必要了,可以直接return false。那应该怎么办呢?
……半个小时过去了,我啥也没有想出来。感觉额外增加的开销实在是太大了。
于是我果断转战评论区,试图找出大家的优秀代码,我一个一个试了过去,发现有一个代码跑出了比较好的结果……我直接把那位老哥的代码复制过来了:
一位老哥的代码
//老哥的代码
bool isSametree(struct TreeNode* s, struct TreeNode* t)
{
if((s == NULL)&&(t == NULL))
return true;
if((s == NULL)||(t == NULL))
return false;
if(s->val == t->val)
return isSametree(s->left, t->left) && isSametree(s->right, t->right);
else
return false;
}
bool isSubtree(struct TreeNode* s, struct TreeNode* t){
if(s == NULL)
return false;
else
return isSametree(s, t) || isSubtree(s->left, t) || isSubtree(s->right, t);
}
作者:p8salon
链接:https://leetcode-cn.com/problems/subtree-of-another-tree/solution/di-gui-pan-duan-shi-xiang-tong-de-shu-yi-ding-shi-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
没错,这位老哥的代码没有任何格式,没有写任何缩进……但是效率却达到了惊人的99.54%:
老哥运行结果
//整容代码(我真的嫌老哥的代码太丑,稍微缩进整容了一下)
bool isSametree(struct TreeNode* s, struct TreeNode* t)
{
if((s == NULL)&&(t == NULL))
return true;
if((s == NULL)||(t == NULL))
return false;
if(s->val == t->val)
return isSametree(s->left, t->left) && isSametree(s->right, t->right);
else
return false;
}
bool isSubtree(struct TreeNode* s, struct TreeNode* t){
if(s == NULL)
return false;
else
return isSametree(s, t) || isSubtree(s->left, t) || isSubtree(s->right, t);
}
仔细看看技术细节……貌似没有和我写的差很多啊……
这题虽然解出来了,但是总感觉差了点味道,我也不明白为什么我的代码效率会比这位老哥的要低。可能又是因为什么编译原因吧。