剑指Offer_#26_树的子结构
Contents
题目
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
示例 1:
输入:A = [1,2,3], B = [3,1]
输出:false
示例 2:
输入:A = [3,4,5,1,2], B = [4,1]
输出:true
限制:
0 <= 节点个数 <= 10000
思路分析
题意分析
题意有一点模糊的地方,就是没讲清楚输入数组的形式代表的是哪种遍历的序列,经过测试,这个代表的是广度优先遍历的序列,也就是按照从上到下,从左到右的顺序进行遍历。
思路
主要分为两个步骤,
1.第一个步骤是找到A
当中与B
的根节点相同的节点
2.第二个步骤是从这个相同的节点出发,判断两个节点的子树的结构是否相同
两个步骤都可以通过递归进行前序遍历,分别分析两个递归代码。
步骤1:遍历A
寻找相同节点
- 出口条件:
- A和B其中一个是
null
,返回false- A是null:说明将整个父树遍历完了,还没有找到与B相同的子结构
- B是null:注意到递归过程中,B是null,说明输入的B就是null,根据题目中条件,空树不是任意一个树的子结构,直接返回false
A.val == B.val
,说明找到了与A相同的节点,调用判断子树是否相同的函数进一步判断,如果结果为true,就返回true
- A和B其中一个是
- 递推过程:
- 继续判断从A的左子节点和右子节点出发,子树结构是否和
B
相同。返回二者的或。
- 继续判断从A的左子节点和右子节点出发,子树结构是否和
步骤2:遍历A
和B
判断子树结构是否相同
写法1:逆向思维,如果从来没有不相同的节点,所有节点都相同
-
出口条件
- B已经遍历完成,即遇到
B == null
,返回true (隐含的逻辑是:之前一直没有返回false,才可以到这一步,所以之前的所有对应节点都是相同的) - A已经遍历完成且B还没遍历完,子树结构不可能相同,返回false
- A和B的值不同,子树结构不可能相同,返回false
- B已经遍历完成,即遇到
-
递推过程
- 判断A的左右子树与B的左右子树结构是否相同。
写法2:正向思维,如果每个节点都相同,那么所有节点相同
与写法1的区别在于,这里边有一个“与”的逻辑,也就是说,每一次递归调用时,A与B的节点都相同,才可以保证所有节点都相同。
这样写是比较麻烦的,因为这意味着,需要单独维护一个全局变量isSubTreeSame
,在每个递归过程中更新这个变量。
相比写法1修改的地方
- 出口条件:
- 当B遍历结束时,返回
isSubTreeSame
- 当B遍历结束时,返回
- 递推过程:
- 更新变量
isSubTreeSame
:isSubTreeSame = (isSubTreeSame && (A.val == B.val));
- 更新变量
解答
解答1:逆向思维
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
if(A == null || B == null) return false;
if(A.val == B.val){//在A中寻找B的根节点,找到后就继续判断此根节点的子树是否相同
if(isSubTreeSame(A,B)) return true;
}
return isSubStructure(A.left,B) || isSubStructure(A.right,B);
}
//输入两个根节点,判断子树结构是否相同
public boolean isSubTreeSame(TreeNode A,TreeNode B){
if(B == null) return true;//B树遍历完的时候,且从未出现节点值不相等的情况,就证明是相同的结构
if(A == null && B != null) return false;
if(A.val != B.val) return false;//这个出口条件是反着定义的,因为如果判断true,需要多次比较都相同,是一个且的逻辑,不太好写
return isSubTreeSame(A.left,B.left) && isSubTreeSame(A.right,B.right);
}
}
解答2:正向思维
- class Solution {
- boolean isSubTreeSame;
- public boolean isSubStructure(TreeNode A, TreeNode B) {
- if(A == null || B == null) return false;
- if(A.val == B.val){//在A中寻找B的根节点,找到后就继续判断此根节点的子树是否相同
- isSubTreeSame = true;//如果使用正的出口条件,需要在每次进入比较之前就对这个变量进行重置
- if(isSubTreeSame(A,B)) return true;
- }
- return isSubStructure(A.left,B) || isSubStructure(A.right,B);
- }
- //输入两个根节点,判断子树结构是否相同
- public boolean isSubTreeSame(TreeNode A,TreeNode B){
- if(B == null) return isSubTreeSame;
- if(A == null) return false;
- isSubTreeSame = (isSubTreeSame && (A.val == B.val));
- return isSubTreeSame(A.left,B.left) && isSubTreeSame(A.right,B.right);
- }
- }
注意: 由于引入了额外的变量isSubTreeSame
,需要在进入函数isSubTreeSame()
之前,对变量进行重置(第6行),否则会影响到下一次调用的结果。