LeetCodeCampsDay14-二叉树part02 
继续使用深度/广度遍历解决问题,包含迭代&递归的方法
 
翻转二叉树 
https://leetcode.cn/problems/invert-binary-tree/ 
给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
示例 1: 
1 2 输入:root = [4,2,7,1,3,6,9] 输出:[4,7,2,9,6,3,1] 
 
示例 2: 
1 2 输入:root = [2,1,3] 输出:[2,3,1] 
 
示例 3: 
 
提示: 
树中节点数目范围在 [0, 100] 内 
-100 <= Node.val <= 100 
 
递归思路 
可以发现想要翻转它,其实就把每一个节点的左右孩子交换一下就可以了。
可以按“前序/中序/后序”的方法,将中间节点进行“调换”,然后用递归的方式去处理左、右子树 
递归三步走:返回值与输入值;终止条件(传入node为空);单层递归的逻辑(调换node的左右子树,再执行递归处理左、右子树) 
 
递归代码 
前序递归 
时间复杂度O(N) 
空间复杂度O(h),其中,h为树的高度,最坏情况下递归栈的空间用调用O(N);而最好情况下,即平衡二叉树O(logN) 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class  Solution :    def  traverse (self, node: TreeNode ):         if  not  node:             return                   node.left, node.right = node.right, node.left         self.traverse(node.left)          self.traverse(node.right)     def  invertTree (self, root: Optional [TreeNode] ) -> Optional [TreeNode]:         self.traverse(root)         return  root 
 
中序递归 
这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转了两次!建议拿纸画一画,就理解了 
这题目如果使用中序遍历,需要修改逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 class  Solution :    def  traverse (self, node: TreeNode ):         if  not  node:             return                   self.traverse(node.left)                    node.left, node.right = node.right, node.left         self.traverse(node.left)       def  invertTree (self, root: Optional [TreeNode] ) -> Optional [TreeNode]:         self.traverse(root)         return  root 
 
后序递归 
后序递归不用进行特殊处理
1 2 3 4 5 6 7 8 9 10 11 12 13 class  Solution :    def  traverse (self, node: TreeNode ):         if  not  node:             return                   self.traverse(node.left)          self.traverse(node.right)                  node.left, node.right = node.right, node.left     def  invertTree (self, root: Optional [TreeNode] ) -> Optional [TreeNode]:         self.traverse(root)         return  root 
 
当然,还可以使用栈的方式做这题目(栈+迭代,可以达到递归的效果)
迭代代码(标记法) 
时间复杂度O(N) 
空间复杂度O(h)–和递归一样,最坏情况为O(N),最好情况为O(logN) 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def  invertTree (self, root: Optional [TreeNode] ) -> Optional [TreeNode]:    stack = [(root, False )] if  root else  []          while  stack:         node, visit_status = stack.pop()         if  visit_status:             node.left, node.right = node.right, node.left         else :             stack.append((node, True ))             if  node.left:                 stack.append((node.left, False ))             if  node.right:                 stack.append((node.right, False ))     return  root 
 
当然,也可以使用普通的迭代方法,下面是前序迭代遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def  invertTree (self, root: Optional [TreeNode] ) -> Optional [TreeNode]:    stack = list ()     stack.append(root)     if  not  root:         return  root          while  stack:         node = stack.pop()                  node.left, node.right = node.right, node.left         if  node.left:             stack.append(node.left)         if  node.right:             stack.append(node.right)     return  root 
 
对称二叉树 
https://leetcode.cn/problems/symmetric-tree/ 
给你一个二叉树的根节点 root , 检查它是否轴对称。
示例 1: 
1 2 输入:root = [1,2,2,3,4,4,3] 输出:true 
 
示例 2: 
1 2 输入:root = [1,2,2,null,3,null,3] 输出:false 
 
提示: 
树中节点数目在范围 [1, 1000] 内 
-100 <= Node.val <= 100 
 
**进阶:**你可以运用递归和迭代两种方法解决这个问题吗?
思路 
如果使用递归的方法解决,仍然需要解决三个问题
输入与输出;因为需要判断二叉树是否对称,每次需要判断“左、右”两个节点是否相等,所以输入是leftNode, rightNode 
终止条件:如果leftNode和rightNode都为空,也算是True;leftNode不空而rightNode为空,算False;leftNode为空而rightNode不空,算False;若leftNode和rightNode都不空,但数值不相同,也算False;只有当leftNode和rightNode数值相等时,才进行它俩的子树判断 
单层递归的操作逻辑:先判断终止条件;当leftNode和rightNode数值相等时,才进行它俩的子树判断,这里要判断
leftNode的左子树与rightNode的右子树是否相等(外部判断) 
leftNode的右子树与rightNode的左子树是否相等(内部判断) 
只有内、外都相等时才返回True 
 
 
 
 
 
代码 
时间复杂度O(N) 
空间复杂度O(h)—同样,因为递归调用的空间取决于树的深度,最差情况是O(N),最好情况是O(logN) 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class  Solution :    def  traverse (self, leftNode: TreeNode, rightNode: TreeNode ) -> bool :         if  leftNode is  None  and  rightNode is  not  None :             return  False          elif  leftNode is  not  None  and  rightNode is  None :             return  False          elif  leftNode is  None  and  rightNode is  None :             return  True          elif  leftNode.val != rightNode.val:               return  False          else :                          outSide = self.traverse(leftNode.left, rightNode.right)             inSide = self.traverse(leftNode.right, rightNode.left)             res = outSide and  inSide              return  res     def  isSymmetric (self, root: Optional [TreeNode] ) -> bool :         if  not  root:             return  False          return  self.traverse(root.left, root.right) 
 
迭代思路 
使用队列或栈,每次从栈/队列中取出两个节点,判断它们是否(值一样或都为空),然后再判断他俩的内、外子树是否一致 
 
栈+迭代代码 
时间复杂度O(N) 
空间复杂度O(h)—同样,因为栈占用的空间取决于树的深度,最差情况是O(N),最好情况是O(logN) 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def  isSymmetric (self, root: Optional [TreeNode] ) -> bool :    stack = list ()     if  not  root:         return  False      stack.append(root.left)     stack.append(root.right)     while  stack:                  rightNode = stack.pop()         leftNode = stack.pop()         if  not  leftNode and  not  rightNode:             continue          if  not  leftNode or  not  rightNode or  leftNode.val != rightNode.val:             return  False                                     stack.append(leftNode.left)         stack.append(rightNode.right)                  stack.append(leftNode.right)         stack.append(rightNode.left)     return  True  
 
队列+迭代代码 
把栈(list)换成队列即可
时间复杂度O(N) 
空间复杂度O(h)—同样,因为队列占用的空间取决于树的深度,最差情况是O(N),最好情况是O(logN) 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 from  collections import  dequeque = deque() if  not  root:    return  False  que.append(root.left) que.append(root.right) while  que:         leftNode = que.popleft()     rightNode = que.popleft()          if  not  leftNode and  not  rightNode:         continue           if  not  leftNode or  not  rightNode or  leftNode.val != rightNode.val:         return  False                que.append(leftNode.left)     que.append(rightNode.right)     que.append(leftNode.right)     que.append(rightNode.left) return  True 
 
层序思路 
使用一个队列deque遍历每个节点 
每层再使用一个list,将每层的节点都装进来,并且每层结束后进行清算,如果这层不是对称的,则直接返回False 
 
层序遍历代码 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 if  not  root:    return  False  from  collections import  dequeque = deque() que.append(root.left) que.append(root.right) while  que:    levelSize = len (que)     if  levelSize % 2  != 0 :         return  False           levelRes = list ()     for  i in  range (levelSize):         node = que.popleft()         if  node:             levelRes.append(node.val)             que.append(node.left)             que.append(node.right)         else :             levelRes.append(None )               if  levelRes != levelRes[::-1 ]:         return  False  return  True 
 
相同的树 
https://leetcode.cn/problems/same-tree/ 
给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
示例 1: 
1 2 输入:p = [1,2,3], q = [1,2,3] 输出:true 
 
示例 2: 
1 2 输入:p = [1,2], q = [1,null,2] 输出:false 
 
示例 3: 
1 2 输入:p = [1,2,1], q = [1,1,2] 输出:false 
 
提示: 
两棵树上的节点数目都在范围 [0, 100] 内 
-104 <= Node.val <= 104 
 
递归思路 
和对称二叉树几乎一样,先取两个节点,判断两个节点值是否一样(如果都为空直接就True) 
如果两个节点值一样,再判断他俩的子树,leftNode.left是否等于rightNode.left;并且leftNode.right是否等于rightNode.right;这里和对称二叉树是不一样的,对称二叉树需要判断“内部是否相等,外部是否相等” 
 
递归代码 
时间复杂度O(N) 
空间复杂度O(h)—取决于深度;最坏O(N),最好O(logN) 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class  Solution :    def  traverse (self, leftNode: TreeNode, rightNode: TreeNode ) -> bool :         if  not  leftNode and  not  rightNode:             return  True          elif  not  leftNode and  rightNode:             return  False          elif  leftNode and  not  rightNode:             return  False          elif  leftNode.val != rightNode.val:             return  False          else :                          outSide = self.traverse(leftNode.left, rightNode.left)             inSide = self.traverse(leftNode.right, rightNode.right)             return  outSide and  inSide     def  isSameTree (self, p: Optional [TreeNode], q: Optional [TreeNode] ) -> bool :         return  self.traverse(p, q) 
 
栈+迭代代码 
大体思路和递归差不多;主要是终止条件判断,单层逻辑(判断左边是否一样,判断右边是否一样);两种方法的代码结构几乎一样
时间复杂度O(N) 
空间复杂度O(h)—取决于深度;最坏O(N),最好O(logN) 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 def  isSameTree (self, p: Optional [TreeNode], q: Optional [TreeNode] ) -> bool :         stack = list ()     stack.append(p)     stack.append(q)     while  stack:         rightNode = stack.pop()         leftNode = stack.pop()         if  not  leftNode and  not  rightNode:             continue          elif  not  leftNode and  rightNode:             return  False          elif  leftNode and  not  rightNode:             return  False          elif  leftNode.val != rightNode.val:             return  False          else :                          stack.append(leftNode.left)             stack.append(rightNode.left)                          stack.append(leftNode.right)             stack.append(rightNode.right)          return  True  
 
另一棵树的子树 
https://leetcode.cn/problems/subtree-of-another-tree/ 
给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。
二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。
示例 1: 
1 2 输入:root = [3,4,5,1,2], subRoot = [4,1,2] 输出:true 
 
示例 2: 
1 2 输入:root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2] 输出:false 
 
提示: 
root 树上的节点数量范围是 [1, 2000] 
subRoot 树上的节点数量范围是 [1, 1000] 
-104 <= root.val <= 104 
-104 <= subRoot.val <= 104 
 
暴力思路 
使用前/中/后序进行遍历,并且对每个节点进行匹配 
 
暴力代码 
时间复杂度O(N*M),N为root节点数,M为subRoot节点数 
空间复杂度O(H),然后取决于root树的高度 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class  Solution :    def  isSame (self, leftNode, rightNode ) -> bool :         if  not  leftNode and  not  rightNode:             return  True          elif  not  leftNode or  not  rightNode or  leftNode.val != rightNode.val:             return  False          else :             leftSideSame = self.isSame(leftNode.left, rightNode.left)             rightSideSame = self.isSame(leftNode.right, rightNode.right)             return  leftSideSame and  rightSideSame     def  isSubtree (self, root: Optional [TreeNode], subRoot: Optional [TreeNode] ) -> bool :                                    stack = [(root, False )] if  root else  []         res = False          while  stack:             node, visited = stack.pop()             if  visited:                 res = self.isSame(node, subRoot)                 if  res:                     print (node)                     return  True              else :                                  stack.append((node, True ))                 if  node.left:                     stack.append((node.left, False ))                 if  node.right:                     stack.append((node.right, False ))         return  False              
 
KMP匹配思路 
如果先使用前/中/后序将root与subRoot遍历一遍,得到的不就是两串字符串序列 ,然后就可以使用KMP匹配方法解决 
但,这里有个需要注意的点,不能使用常规的前/中/后序遍历,而是需要使用下面这种方式:引入两个空值 lNull 和 rNull,当一个节点的左孩子或者右孩子为空的时候,就插入这两个空值,这样深度优先搜索序列就唯一对应一棵树 
比如[4,1,2]得到的前序遍历是[4,1,lNone,rNone,2,lNone,rNone] 
而[4,1]得到的前序遍历是[4,1,lNone,rNone,rNone] 
 
 
如果不这样做,会遇到一类错误,比如root是[3,4,5,1,2,null,null,null,null,0];而subRoot是[4,1,2] 
 
使用普通前序遍历后,得到[3, 4, 1,  2,  0,  5]和[4,1,2],如果直接使用KMP进行匹配,会得到True的错误结果; 
使用修改后的前序遍历,得到[3, 4, ‘r’, ‘l’, 1, ‘r’, 2, ‘r’, ‘l’ , 0, ‘r’, ‘l’, 5] 和 [4, ‘r’, ‘l’, 1, ‘r’, ‘l’, 2],此时再用KMP匹配就正确了 
 
 
 
KMP代码 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 class  Solution :              def  preOrder (self, root ) -> list :         stack = [(root, False )] if  root else  []         res = list ()         while  stack:             node, visited = stack.pop()             if  visited:                 res.append(node.val)                                  if  not  node.left:                     res.append("l" )                 if  not  node.right:                     res.append('r' )             else :                                  stack.append((node, True ))                 if  node.left:                     stack.append((node.left, False ))                 if  node.right:                     stack.append((node.right, False ))                  res = res[::-1 ]         return  res     def  getNext (self, s: list  ) -> list :         L = len (s)         nextArray = [0 ] * L         left = 0          for  right in  range (1 , L):             while  left > 0  and  s[left] != s[right]:                 left = nextArray[left - 1 ]             if  s[left] == s[right]:                 left = left + 1              nextArray[right] = left         return  nextArray     def  isSubtree (self, root: Optional [TreeNode], subRoot: Optional [TreeNode] ) -> bool :         rootList = self.preOrder(root)         subRootList = self.preOrder(subRoot)         nextArray = self.getNext(subRootList)                           left = 0          L_subRoot = len (subRootList)         L_root = len (rootList)         for  right in  range (L_root):             while  left > 0  and  rootList[right] != subRootList[left]:                 left = nextArray[left - 1 ]             if  rootList[right] == subRootList[left]:                 left = left + 1              if  left == L_subRoot:                 return  True          return  False  
 
或者使用递归版本的前序遍历
1 2 3 4 5 6 7 8 9 10 11 12 def  preOrderTraverse (self, node, res ):    if  not  node:         return      res.append(node.val)     if  node.left:         self.preOrderTraverse(node.left, res)     else :         res.append('l' )     if  node.right:         self.preOrderTraverse(node.right, res)     else :         res.append('r' ) 
 
二叉树的最大深度 
https://leetcode.cn/problems/maximum-depth-of-binary-tree/ 
给定一个二叉树 root ,返回其最大深度。
二叉树的 最大深度  是指从根节点到最远叶子节点的最长路径上的节点数。
示例 1: 
1 2 输入:root = [3,9,20,null,null,15,7] 输出:3 
 
示例 2: 
1 2 输入:root = [1,null,2] 输出:2 
 
提示: 
树中节点的数量在 [0, 104] 区间内。 
-100 <= Node.val <= 100 
 
后序递归思路 
补充下什么是深度与高度,这里要求“最大深度”,其实就是求“根节点的高度”
根节点的高度就是二叉树的最大深度 ,所以本题中我们通过后序求的根节点高度 来求的二叉树最大深度 
确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回这棵树的深度,所以返回值为int类型 
确定终止条件:如果为空节点的话,就返回0,表示高度为0 
确定单层递归的逻辑:先求它的左子树的深度,再求右子树的深度,最后取左右深度最大的数值 再+1 (加1是因为算上当前中间节点)就是目前节点为根节点的树的深度。 
 
所以这里的代码也同样是求高度的代码,还是比较好写的
如果要求某个节点所在位置的深度 ,有个思路是,根节点到这个节点的路径是唯一的,可以求出这路径长度,即是深度;至于怎么求这个路径,可以参考day15的「二叉树的所有路径」
后序递归代码 
时间复杂度O(N) 
空间复杂度O(h) 最坏情况O(N) 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class  Solution :    def  traverse (self, node ) -> int :         if  not  node:             return  0                   leftDeepth = self.traverse(node.left)         rightDeepth = self.traverse(node.right)         return  max (leftDeepth, rightDeepth) + 1               def  maxDepth (self, root: Optional [TreeNode] ) -> int :         return  self.traverse(root) 
 
迭代思路 
层序遍历就是最好求深度的过程 
 
迭代代码 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def  maxDepth (self, root: Optional [TreeNode] ) -> int :    from  collections import  deque     if  not  root:         return  0      que = deque()     que.append(root)     level = 0      while  que:         L = len (que)         for  i in  range (L):             node = que.popleft()             if  node.left:                 que.append(node.left)             if  node.right:                 que.append(node.right)                  level += 1      return  level 
 
N 叉树的最大深度 
https://leetcode.cn/problems/maximum-depth-of-n-ary-tree/ 
给定一个 N 叉树,找到其最大深度。
最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。
N 叉树输入按层序遍历序列化表示,每组子节点由空值分隔(请参见示例)。
示例 1: 
1 2 输入:root = [1,null,3,2,4,null,5,6] 输出:3 
 
示例 2: 
1 2 输入:root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14] 输出:5 
 
提示: 
树的深度不会超过 1000 。 
树的节点数目位于 [0, 104] 之间。 
 
迭代思路 
使用层序遍历,和二叉树的层序遍历求最大深度基本一样 
 
迭代代码 
时间复杂度O(N) 
空间复杂度O(N)  which can scale with the tree’s width. 最多是树的宽度 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 """ # Definition for a Node. class Node:     def __init__(self, val: Optional[int] = None, children: Optional[List['Node']] = None):         self.val = val         self.children = children """ class  Solution :    def  maxDepth (self, root: 'Node'  ) -> int :                           from  collections import  deque         if  not  root:             return  0          que = deque()         que.append(root)         levels = 0          while  que:             L = len (que)             for  i in  range (L):                 node = que.popleft()                 for  eachNode in  node.children:                     que.append(eachNode)             levels += 1          return  levels 
 
递归代码 
1 2 3 4 5 6 7 8 9 10 11 12 def  traverse (self, node ) -> int :    if  not  node:         return  0      deepthList = []     for  eachNode in  node.children:         deepthList.append(self.traverse(eachNode))          if  len (deepthList):         return  1  + max (deepthList)          else :         return  1  
 
二叉树的最小深度 
https://leetcode.cn/problems/minimum-depth-of-binary-tree/ 
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
**说明:**叶子节点是指没有子节点的节点。
示例 1: 
1 2 输入:root = [3,9,20,null,null,15,7] 输出:2 
 
示例 2: 
1 2 输入:root = [2,null,3,null,4,null,5,null,6] 输出:5 
 
提示: 
树中节点数的范围在 [0, 105] 内 
-1000 <= Node.val <= 1000 
 
递归后序思路 
求最大深度时,使用了下面的代码
1 2 3 4 int  leftDepth = getDepth(node->left);int  rightDepth = getDepth(node->right);int  result = 1  + min (leftDepth, rightDepth);return  result;
 
如果这么求的话,没有左孩子的分支会算为最短深度。 
所以,如果左子树为空,右子树不为空,说明最小深度是 1 + 右子树的深度 。
反之,右子树为空,左子树不为空,最小深度是 1 + 左子树的深度。 
最后如果左右子树都不为空,返回左右子树深度最小值 + 1 。
求二叉树的最小深度和求二叉树的最大深度的差别主要在于处理左右孩子不为空的逻辑。 
递归后序代码 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class  Solution :    def  traverse (self, node ):         if  not  node:             return  0          leftDeepth = self.traverse(node.left)         rightDeepth = self.traverse(node.right)                  if  not  node.left and  node.right:             return  1  + rightDeepth         if  not  node.right and  node.left:             return  1  + leftDeepth                  return  min (leftDeepth, rightDeepth) + 1      def  minDepth (self, root: Optional [TreeNode] ) -> int :         return  self.traverse(root) 
 
迭代思路 
使用层序遍历即可,但有个退出条件: 
if not node.right and not node.left:
return levels 
 
 
 
不加这行就报错,因为那样就会变成求最大深度了而不是“最小深度”
比如这个例子,如果不加这行,会输出3而不是2
迭代代码 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 def  minDepth (self, root: Optional [TreeNode] ) -> int :         from  collections import  deque     que = deque()     if  not  root:         return  0      que.append(root)     levels = 0      while  que:         L = len (que)         levels += 1          for  i in  range (L):             node = que.popleft()             if  node.left:                 que.append(node.left)             if  node.right:                 que.append(node.right)                                       if  not  node.right and  not  node.left:                 return  levels     return  levels