一、题目
题目链接:力扣
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即A中有出现和B相同的结构和节点值。
例如:
给定的树 A:
3
/ \
4 5
/ \
1 2
给定的树 B:
4
/
1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。
示例 1:
输入:A = [1,2,3], B = [3,1]
输出:false
示例 2:
输入:A = [3,4,5,1,2], B = [4,1]
输出:true
限制:
0 <= 节点个数 <= 10000
二、题解
1、思路
🐰 深度优先+递归
递归中存在递归!(先序遍历递归中存在递归子树判断)
- 首先是DFS(深度优先遍历)找到A中值跟B根节点值(是值,不是结点)一样的结点C
- 找到后判断B是否为C的子树(必须包含C的根节点),此时由于B与C的根节点一样,我们就很好判断
- 如果满足子子树的条件,我们就立刻返回
- 不满足的话,因为A中值与B根节点值相等的结点可能有多个,所以继续dfsA中的其他结点
如果用一句话概括本题的思路:
如果A在根节点处与B不相同,则继续查找A的左子树或右子树,判断和B树是否有相同的子结构!!!
2、代码实现
🐰 深度优先+递归
class Solution{
public:
// 查找A中以B为根节点的子树是否和B相等(递归判断是否相等)
// B第一次进入一定不为空
bool recur(TreeNode* A, TreeNode* B)
{
if(B == NULL)return true;// B为空说明B已经完成递归匹配,确定为子结构
// A为空说明,查找所有A左或是A右都没找到
// AB的值不同也说明不是子结构
if(A == NULL || A->val != B->val)return false;
return recur(A->left, B->left) && recur(A->right, B->right);
}
// 这个函数对这棵树进行前序遍历:
// 即处理根节点,再递归左子节点,再递归处理右子节点
// 特殊情况是:当A或B是空树的时候 返回false
bool isSubStructure(TreeNode* A, TreeNode* B) {
// B为空,空树不是任一树的子树,直接返回flase
// A为空,无论B是否为空,B都不会是子树,返回false
if(A == NULL || B == NULL)return false;
// A中找到与B根节点相等的结点,开始判断B子树是否相等
// B为A的子结构有3种情况,满足任意一种即可:
// 1.B的子结构起点为A的根节点,此时结果为recur(A,B)
// 2.B的子结构起点隐藏在A的左子树中,而不是直接为A的根节点,此时结果为isSubStructure(A.left, B)
// 3.B的子结构起点隐藏在A的右子树中,此时结果为isSubStructure(A.right, B)
//bool status1 = recur(A, B);
// 对左右子数进行判断
//bool status2 = isSubStructure(A->left, B);
//bool status3 = isSubStructure(A->right, B);
//return status1 || status2 || status3;
// 使用这一句替代上方几句,短路运算,更快
// 这里的本质是:
// return recure(A,B) || (A->left,B) || (A->right,B) || ... || recur(A所有结点,B)
return recur(A, B) || isSubStructure(A->left, B)|| isSubStructure(A->right, B);
}
};
3、复杂度分析
🐰 深度优先+递归
时间复杂度 O(MN) :其中 M,N 分别为树 A 和 树 B 的节点数量;先序遍历树 A 占用 O(M) ,每次调用 recur(A, B) 判断占用 O(N) 。
空间复杂度 O(M): 当树 A和树 B 都退化为链表时,递归调用深度最大。当 M≤N 时,遍历树 A 与递归判断的总递归深度为 M ;当 M>N 时,最差情况为遍历至树 A 叶子节点,此时总递归深度为 M。
注1:递归函数空间复杂度与系统堆栈有关,系统栈需要记住每个节点的值,我自己理解的递归一次空间复杂度+O(1)。
注2:二叉树先序遍历时间复杂度和空间复杂度为O(n)。二叉树的前序遍历、中序遍历、后序遍历、层序遍历的时间复杂度和空间复杂度
4、运行结果
🐰 深度优先+递归