LeetCode:二叉树(五)

本组囊括二叉树中匹配类问题

对于二叉树的题目,无非就以下几种解题思路:

先序遍历(深度优先搜索);

中序遍历(深度优先搜索)(尤其二叉搜索树);

后序遍历(深度优先搜索);

层序遍历(广度优先搜索)(尤其按照层来解决问题的时候);

序列化与反序列化(结构唯一性问题),如剑指offer 37;

匹配类问题;


本组介绍最后一类匹配类问题,匹配类二叉树可以使用一种套路相对固定的递归函数,在周赛中和每日一题中多次出现,而第一次见到不太容易写出正确的递归解法,因此这里来总结一下。

这类题目与字符串匹配有些神似,求解过程大致分为两步:

先将根节点匹配;

根节点匹配后,对子树进行匹配。

而参与匹配的二叉树可以是一棵,与自身匹配;也可以是两棵,即互相匹配。

比如「101. 对称二叉树」就是两棵树之间的匹配问题。为了更具一般性,我们先来看「面试题 04.10. 检查子树」这道题。

面试题 04.10. 检查子树

难度:中等

 思路:

        匹配类典型题目,此题归于自身匹配问题,使用一个递归函数dfs;
        此题思路为:先判断t的根节点是否是s的节点之一,在确定根节点的情况下使用递归函数dfs去匹配二叉树;
        确定根节点为C后,判断t树是否是C树的子树。

解法:

 1 class Solution:
 2     def checkSubTree(self, s: TreeNode, t: TreeNode) -> bool:
 3         # 匹配类典型题目,此题归于自身匹配问题,使用一个递归函数dfs
 4         # 此题思路为:先判断t的根节点是否是s的节点之一,在确定根节点的情况下使用递归函数dfs去匹配二叉树
 5         # 确定根节点为C后,判断t树是否是C树的子树
 6         
 7         # 先写dfs函数
 8         def dfs(s, t):
 9             if not s and not t: # 两棵树都到达空节点,说明完全匹配
10                 return True
11             if not s or not t: # 两棵树有一颗先到达了空节点,说明不完全匹配,按照题意这种的所有类型都不属于子树
12                 return False
13             # 其他情况放进return内考虑
14             return s.val == t.val and dfs(s.left, t.left) and dfs(s.right, t.right)
15 
16         # 再写主函数,宏观上来看,主函数就是先去匹配根,在匹配到根节点的情况下,去执行递归函数dfs,相当于双重递归。
17         # 这里用一种统一的模板来写主函数,可用于这些两棵树匹配的题目:
18         if not t or not s: #
19             return False
20         
21         if dfs(s, t): # 目前传入的两棵树满足匹配条件,直接返回
22             return True
23         # 否则继续遍历s树的根,去一一和t树匹配,只要匹配到一个点即可返回真
24         return self.checkSubTree(s.left, t) or self.checkSubTree(s.right, t)

 

剑指 Offer 26. 树的子结构

难度:中等

 思路:这题和上题的区别即为B可不用到达A的叶子节点

  先将根节点匹配,于是主函数的目的就是找到A树中根节点和B根节点值相同的节点,然后开始调用辅助函数去递归判断子树结构是否匹配。
       辅助函数dfs作用即为确定了A B 根节点后,同时往下递归匹配,由于题目只要求B是A的一部分,不一定走到叶子节点,所以B走到叶子节点后就可以返回True,如果A先走到了叶子节点说明不匹配,返回False,当然递归还要判断A/B当前节点值是否相等,这些可以放到返回值中。
        这样我们可以定下函数dfs的输入输出和作用:
        输入:根节点A,根节点B
        返回值: ture/false, 当继续遍历时继续递归当前值是否相等and A/B的左右子树匹配。
        将视野放远来看,主函数则解决了如何确定 A 的哪个节点是 B 的根节点。
        如果 A 的当前节点值与 B 的根节点值相同,我们调用 dfs 函数判断子树是否也相同;如果不同,我们就递归调用主函数来寻找 A 的哪个节点与 B 的根节点匹配。

解法:

 1 class Solution:
 2     def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
 3         # 大神总结:递归 深度优先搜索
 4         # 匹配类二叉树可以使用一种套路相对固定的递归函数,在周赛中和每日一题中多次出现,而第一次见到不太容易写出正确的递归解法,因此我们来总结一下。(注:不要太纠结于名字,因为名字是我自己起的......)
 5         # 这类题目与字符串匹配有些神似,求解过程大致分为两步:
 6         # 先将根节点匹配;
 7         # 根节点匹配后,对子树进行匹配。
 8         # 而参与匹配的二叉树可以是一棵,与自身匹配;也可以是两棵,即互相匹配。
 9         # 比如「101. 对称二叉树」就是两棵子树之间的匹配问题。为了更具一般性,我们先来看「面试题 04.10. 检查子树」这道题。
10 
11         # 思路一:
12         # 1.先将根节点匹配,于是主函数的目的就是找到A树中根节点和B根节点值相同的节点,然后开始调用辅助函数去递归判断子树结构是否匹配。
13         # 辅助函数dfs作用即为确定了A B 根节点后,同时往下递归匹配,由于题目只要求B是A的一部分,不一定走到叶子节点,所以B走到叶子节点后就可以返回True,如果A先走到了叶子节点说明不匹配,返回False,当然递归还要判断A/B当前节点值是否相等,这些可以放到返回值中。
14         # 这样我们可以定下函数dfs的输入输出和作用:
15         # 输入:根节点A,根节点B
16         # 返回值: ture/false, 当继续遍历时继续递归当前值是否相等and A/B的左右子树匹配
17         # 将视野放远来看,主函数则解决了如何确定 A 的哪个节点是 B 的根节点。
18         # 如果 A 的当前节点值与 B 的根节点值相同,我们调用 dfs 函数判断子树是否也相同;如果不同,我们就递归调用主函数来寻找 A 的哪个节点与 B 的根节点匹配。
19         def dfs(root1, root2): # root1为主树
20             if not root2: # 两个一起遍历下来,都匹配,然后B树空了,说明完全匹配了,这里是这道题的关键!! 只需要B树遍历为空就行,A树不管
21                 return True
22             if not root1: # root1先到底了
23                 return False
24             if root1.val != root2.val:
25                 return False
26             return dfs(root1.left, root2.left) and dfs(root1.right, root2.right)
27         
28         # 来看主函数
29         if not A or not B:
30             return False # 如果A或B本身为空,根据题意,肯定不匹配了
31         if dfs(A, B): # 目前传入的两棵树满足匹配条件,直接返回
32             return True
33         # 否则继续遍历A树的根,去一一和B树匹配,只要匹配到一个点即可返回真
34         return self.isSubStructure(A.left, B) or self.isSubStructure(A.right, B)
35         # 时间复杂度: O(M+N),遍历两棵树节点数
36         # 空间复杂度:O(M), 递归的栈最多深度为A的节点数,当A/B 退化为链表

 

100. 相同的树

难度:简单

思路:

此题和上题基本一样,不过更为简单,无需dfs函数,主函数逐一递归即可。

解法:

 1 class Solution:
 2     def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
 3         # 标签:深度优先遍历,使用递归实现深度优先遍历
 4         # 对于这题来说,也是二叉树的先序遍历(先根后左最后右)
 5         # 方法一:递归解法,首先判断q、p是否都为None,不是的话再检测是否其中一个为None或是两个的值相等,都满足的话就继续判断p、q的左右子节点。
 6         if not p and not q: # p,q均为None
 7             return True
 8         if not p or not q: # p、q其中一个为none
 9             return False
10         if p.val != q.val:  # 都不为None但值不相等的情况
11             return False
12         return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right) # 递归判断两个子节点
13         # 时间复杂度:O(N),其中 N 是树的结点数,因为每个结点都访问一次。
14         # 空间复杂度: 最优情况(完全平衡二叉树)时为O(log(N)),最坏情况下(完全不平衡二叉树)时为O(N),用于维护递归栈。
15         
16         # 方法二:
17         # 迭代法,常用的使用队列使递归转迭代方法
18         # 创造一个单向队列,先将树压入
19         queue = [p, q] 
20         while queue: # 队列为空循环终止
21             left = queue.pop(0)
22             right = queue.pop(0)
23             if not left and not right:
24                 continue
25             if not left or not right:
26                 return False
27             if left.val != right.val:
28                 return False
29             queue.append(left.left) # 添加左节点的左儿子
30             queue.append(right.left) # 右节点的左儿子
31             queue.append(left.right) # 左节点的右儿子
32             queue.append(right.right) # 右节点的右儿子
33         return True

下面是一道和自身匹配的题目:

101. 对称二叉树

难度:简单

 思路:

将自身看作两棵树,用左子树和右子树镜像比较;具体看注释。

解法:

 1 class Solution:
 2     def isSymmetric(self, root: TreeNode) -> bool:
 3         # 解法一
 4         # 递归 深度优先遍历/ 二叉树的先序遍历
 5         # 在根节点值相等的情况下 递归地比较左子树的左节点和右子树的右节点,然后是左子树的右节点和右子树的左节点
 6         # if not root:
 7         #     return True
 8         # def dfs(left, right):
 9         #     if not left and not right:# 左子树和右子树均为空
10         #         return True
11         #     if not left or not right: # 左子树和右子树有一个为空
12         #         return False
13         #     if left.val != right.val:
14         #         return False
15         #     return dfs(left.left, right.right) and bfs(left.right, right.left)
16         # return dfs(root.left, root.right)
17         # 时间复杂度:O(N),N为树的节点数。
18         # 空间复杂度:最差O(N)。N为树的高度
19 
20         # 解法二:迭代
21         # 树的迭代一般通过借助队列来完成:递归转迭代
22         # 首先我们把根节点的左右子节点加入队列,比较法则仍然是通递归一样,但如果左右均为空则循环继续
23         # 然后再在队列中添加左节点的左儿子,右节点的右儿子,左节点的右儿子,右节点的左儿子,依次比较
24         # 循环结束条件为队列为空或是我们判断出了不对称的情况
25         if not root or not (root.left or root.right):
26             return True
27         queue = [root.left, root.right] # 数组实现队列
28         while queue: # 队列非空
29             left = queue.pop(0) # 用pop(0)实现队列先入先出
30             right = queue.pop(0)
31             if not left and not right:
32                 continue
33             if not left or not right:
34                 return False
35             if left.val != right.val:
36                 return False
37             queue.append(left.left) # 左节点的左孩子
38             queue.append(right.right) # 右节点的右孩子
39             queue.append(left.right)  # 左节点的右孩子
40             queue.append(right.left)  # 右节点的左孩子,这四个全部加入队列,循环每次只比较前两个节点值,找到不对称或是队列为空循环终止
41         return True
42         # 时间复杂度:O(N),N为树的节点数。
43         # 空间复杂度:O(N),维护最多N个节点的队列

再来看几道类似的匹配类题目:

110. 平衡二叉树

难度:简单

思路:

此题和上题基本一样,不同的是dfs函数需要判断高度,具体看注释

解法:

 1 class Solution:
 2     def height(self, root):
 3             if not root: # 递归终止条件
 4                 return 0
 5             else:
 6                 return max(self.height(root.left), self.height(root.right)) + 1
 7     def isBalanced(self, root: TreeNode) -> bool:
 8         # 模式:递归,深度优先搜索
 9         # 做好一个节点应该做好的事
10         # 这里需要一个height方法,计算子树的高度
11         # 解法一: 自顶向下的递归,判断子树的绝对值小于等于1后继续往下判断
12         # 缺点:由于引入一个方法height,且在该方法中也用到了递归,整体也用了递归,所以复杂度有点爆表
13         
14         if not root:
15             return True
16         if abs(self.height(root.left) - self.height(root.right)) > 1:
17             return False
18         return self.isBalanced(root.left) and self.isBalanced(root.right)
19         
20         # 时间复杂度:O(NlogN): 最差情况下,isBalanced(root) 遍历树所有节点,占用O(N);判断每个节点的最大高度 height(root) 需要遍历各子树的所有节点,子树的节点数的复杂度为 O(logN).
21         # 空间复杂度O(N): 最差情况下(树退化为链表时),系统递归需要使用O(N) 的栈空间。

剑指 Offer 27. 二叉树的镜像

难度:简单

思路:

此题要求输出树的镜像,建一颗树,dfs生成即可

解法:

 1 class Solution:
 2     def mirrorTree(self, root: TreeNode) -> TreeNode:
 3         # 有点像主站的对称二叉树
 4         # 弄清楚逻辑,其实镜像就是根>右>左的顺序往下复制
 5         # 思路一:递归,深度优先搜索
 6         # 根据二叉树镜像的定义,考虑递归遍历(dfs)二叉树,交换每个节点的左 / 右子节点,即可生成二叉树的镜像。
 7         # 注意,不用生成新的二叉树,交换原来树的左右节点即可
 8         # DFS
 9         if not root: # 深度到越过叶子节点
10             return None
11         temp = root.left # 用一个值先保存原来root的左孩子,因为下面会改变
12         root.left = self.mirrorTree(root.right)
13         root.right = self.mirrorTree(temp)
14         return root
15         # 时间复杂度:O(N)
16         # 空间复杂度: 树的高度,最坏O(n),最好O(logn)
17 
18         # 思路二: 迭代,用队列,广度优先搜索
原文地址:https://www.cnblogs.com/Jesee/p/13970310.html