zoukankan      html  css  js  c++  java
  • 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         # 思路二: 迭代,用队列,广度优先搜索
  • 相关阅读:
    【BZOJ2127】happiness 最小割
    【xsy2748】 fly 矩阵快速幂
    [BZOJ2758] [SCOI2012]Blinker的噩梦 扫描线+set
    【BZOJ2732】【HNOI2012】射箭 二分+半平面交
    【xsy1162】鬼计之夜 最短路+二进制拆分
    【xsy2111】 【CODECHEF】Chef and Churus 分块+树状数组
    【xsy1116】数学题 奥数题
    【CODECHEF】Children Trips 倍增
    【xsy1098】第k小 可持久化trie
    扩展中国剩余定理(扩展CRT)详解
  • 原文地址:https://www.cnblogs.com/Jesee/p/13970310.html
Copyright © 2011-2022 走看看