二叉树系列3
- 二叉树 看到二叉树就想到递归
- 404 左叶子之和
- 重点
- 代码随想录的代码
- 我的代码(当日晚上自己理解后写)
- 513 找树左下角的值
- 重点
- 代码随想录的代码
- 我的代码(当日晚上自己理解后写)
- 我去,我怎么能写出这样的代码,没有return的递归,大错特错!
- 修正
- 112 路径总和
- 未看讲解,自己编写的青春稚嫩版
- 重点
- 代码随想录的代码
- 我的代码(当日晚上自己理解后写)
- 113 路径总和2
- 未看解答前自己编写
- 重点
- 代码随想录的代码
- 我的代码(当日晚上自己理解后写)
- 106 从中序与后序遍历序列构造二叉树
- 重点
- 代码随想录的代码
- 我的代码(当日晚上自己理解后写)
- 105 从前序与中序遍历序列构造二叉树
- 重点
- 代码随想录的代码
- 我的代码(当日晚上自己理解后写)
- 654 最大二叉树
- 重点
- 代码随想录的代码
- 我的代码(当日晚上自己理解后写)
- 二叉树系列3总结
二叉树 看到二叉树就想到递归
404 左叶子之和
不会,单次递归中的处理逻辑没想清楚。
重点
首先要理清左叶子节点的定义,即当前节点是其父节点的左孩子,并且当前节点的左右孩子均为None。
其次在理清递归逻辑时注意,在我们的定义中,每个节点收集的是其左右子树的左叶子之和,所以当遍历至叶子节点时,return 0
遍历顺序:使用后序遍历,同样是从底向上收集信息的,后序遍历写的代码较为简洁。
左递归后的,if 条件判断赋值,是整个代码的关键,画个图就能理解,意义为:子树为[9 6 7],那么对于9这个节点,左叶子之和就为6 。这里的内容是:确定单层递归逻辑里。
解二叉树的题目时,已经习惯了通过节点的左右孩子判断本节点的属性,而本题我们要通过节点的父节点判断本节点的属性。
代码随想录的代码
递归法:
class Solution:def sumOfLeftLeaves(self, root):if root is None:return 0if root.left is None and root.right is None:return 0leftValue = self.sumOfLeftLeaves(root.left) # 左if root.left and not root.left.left and not root.left.right: # 左子树是左叶子的情况leftValue = root.left.valrightValue = self.sumOfLeftLeaves(root.right) # 右sum_val = leftValue + rightValue # 中return sum_val
迭代法(前序遍历,中左右,思路上很直接,按照定义,统计所有的左孩子即可)
class Solution:def sumOfLeftLeaves(self, root):if root is None:return 0st = [root]result = 0while st:node = st.pop()if node.left and node.left.left is None and node.left.right is None:result += node.left.valif node.right:st.append(node.right)if node.left:st.append(node.left)return result
我的代码(当日晚上自己理解后写)
class Solution:def sumOfLeftLeaves(self, root: Optional[TreeNode]) -> int:if root == None :return 0leftsum = self.sumOfLeftLeaves(root.left)rightsum = self.sumOfLeftLeaves(root.right)if root.left == None :return rightsumif root.left.left == None and root.left.right == None :return leftsum+rightsum+root.left.valelse :return leftsum+rightsum
迭代法,二刷再写。
513 找树左下角的值
不会,用层序遍历很简单啊,为什么没想到,用层序遍历!!!
同样,递归法也要学习,这里的递归也要用回溯。
重点
首先是遍历顺序,只要保证,先遍历左节点就可以了,前中后序(中左右,左中右,左右中,左均在右前,所以什么顺序都可以),所以在递归中,用前序遍历。
代码随想录的代码
递归+回溯
class Solution:def findBottomLeftValue(self, root: TreeNode) -> int:self.max_depth = float('-inf')self.result = Noneself.traversal(root, 0)return self.resultdef traversal(self, node, depth):if not node.left and not node.right:if depth > self.max_depth:self.max_depth = depthself.result = node.valreturnif node.left:depth += 1self.traversal(node.left, depth)depth -= 1if node.right:depth += 1self.traversal(node.right, depth)depth -= 1
递归+隐式回溯
class Solution:def findBottomLeftValue(self, root: TreeNode) -> int:self.max_depth = float('-inf')self.result = Noneself.traversal(root, 0)return self.resultdef traversal(self, node, depth):if not node.left and not node.right:if depth > self.max_depth:self.max_depth = depthself.result = node.valreturnif node.left:self.traversal(node.left, depth+1)if node.right:self.traversal(node.right, depth+1)
层序遍历:
from collections import deque
class Solution:def findBottomLeftValue(self, root):if root is None:return 0queue = deque()queue.append(root)result = 0while queue:size = len(queue)for i in range(size):node = queue.popleft()if i == 0:result = node.valif node.left:queue.append(node.left)if node.right:queue.append(node.right)return result
我的代码(当日晚上自己理解后写)
自己编写时,第一时间依旧写不出来,原因是:不知道如何在递归中去判断,当前是否是最后一层。学习解答中的思路,用一个全局变量去判断当前是否为 max.depth。
迭代法,二刷再写。
我去,我怎么能写出这样的代码,没有return的递归,大错特错!
class Solution:def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:if root.left == None and root.right == None :return root.valself.maxdepth = -1self.rleft = 0self.digui(root,0)return self.rleftdef digui(self,root,depth):if self.maxdepth < depth :self.maxdepth = depthself.rleft = root.valif root.left :self.digui(root.left,depth+1)if root.right :self.digui(root.right,depth+1)
上述代码也能通过的原因:在Python中,不写return,会默认在程序结束后,return None
修正
class Solution:def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:if root.left == None and root.right == None :return root.valself.maxdepth = -1self.rleft = 0self.digui(root,0)return self.rleftdef digui(self,root,depth):if root.left == None and root.right ==None :if self.maxdepth < depth :self.maxdepth = depthself.rleft = root.valreturnif root.left :self.digui(root.left,depth+1)if root.right :self.digui(root.right,depth+1)
112 路径总和
未看讲解,自己编写的青春稚嫩版
class Solution:def __init__(self):self.bool = Falsedef hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:res = 0if root == None:return Falseself.digui(root,targetSum,res)return self.booldef digui(self,root,target,res):if not self.bool :if root.left == None and root.right == None :res += root.valif res == target :self.bool = Truereturnelse :returnif root.left :res += root.valleftbool = self.digui(root.left,target,res)res -= root.valif root.right :res += root.valrightbool = self.digui(root.right,target,res)res -= root.val returnelse :return
重点
借助本题,要学习,递归算法,什么时候需要返回值,什么时候不需要返回值?
本题同样不需要对中间节点进行处理逻辑,所以前中后序遍历均可。
本题是需要返回值的,还是要学习代码随想录的代码,及时返回,避免无效递归,比如在 if root.left 后,不仅要进一步递归,还要判断一下,是否return True 。
本题还有另一种方法,迭代法,要学习,用栈模拟需要回溯的递归过程。
代码随想录的代码
递归
class Solution:def traversal(self, cur: TreeNode, count: int) -> bool:if not cur.left and not cur.right and count == 0: # 遇到叶子节点,并且计数为0return Trueif not cur.left and not cur.right: # 遇到叶子节点直接返回return Falseif cur.left: # 左count -= cur.left.valif self.traversal(cur.left, count): # 递归,处理节点return Truecount += cur.left.val # 回溯,撤销处理结果if cur.right: # 右count -= cur.right.valif self.traversal(cur.right, count): # 递归,处理节点return Truecount += cur.right.val # 回溯,撤销处理结果return Falsedef hasPathSum(self, root: TreeNode, sum: int) -> bool:if root is None:return Falsereturn self.traversal(root, sum - root.val)
递归+精简
class Solution:def hasPathSum(self, root: TreeNode, sum: int) -> bool:if not root:return Falseif not root.left and not root.right and sum == root.val:return Truereturn self.hasPathSum(root.left, sum - root.val) or self.hasPathSum(root.right, sum - root.val)
迭代法
class Solution:def hasPathSum(self, root: TreeNode, sum: int) -> bool:if not root:return False# 此时栈里要放的是pair<节点指针,路径数值>st = [(root, root.val)]while st:node, path_sum = st.pop()# 如果该节点是叶子节点了,同时该节点的路径数值等于sum,那么就返回trueif not node.left and not node.right and path_sum == sum:return True# 右节点,压进去一个节点的时候,将该节点的路径数值也记录下来if node.right:st.append((node.right, path_sum + node.right.val))# 左节点,压进去一个节点的时候,将该节点的路径数值也记录下来if node.left:st.append((node.left, path_sum + node.left.val))return False
我的代码(当日晚上自己理解后写)
class Solution:def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:if root == None :return Falseif targetSum - root.val == 0 and root.left == None and root.right == None:return Trueif self.hasPathSum(root.left,targetSum-root.val):return Trueif self.hasPathSum(root.right,targetSum-root.val):return Truereturn False
迭代法,二刷再写。
113 路径总和2
未看解答前自己编写
Debug心得 :主要有两点
1:在获得正确路径后,res要进行一步pop,回溯,因为前面有append操作.
2:Python中,res被append进到level后,如果对res操作,level的值也会改变!离谱!
例如: res = [1,2] r = [] r.append(res) res.pop() 此时,r 中为 [ [ 1 ] ] ,所以要 r.append(res.copy())
class Solution:def __init__(self):self.level = []def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:if root == None :return []res = []self.digui(root,targetSum,res,self.level)return self.leveldef digui(self,root,target,res,level):if root.left == None and root.right == None :target -= root.valif target == 0 :res.append(root.val)self.level.append(res.copy())res.pop()return if root.left :target -= root.valres.append(root.val)self.digui(root.left,target,res,self.level)target += root.valres.pop()if root.right :target -= root.valres.append(root.val)self.digui(root.right,target,res,self.level)target += root.valres.pop()return
此题不需要引入全局变量
class Solution:def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:if root == None :return []level = []res = []self.digui(root,targetSum,res,level)return leveldef digui(self,root,target,res,level):if root.left == None and root.right == None :target -= root.valif target == 0 :res.append(root.val)level.append(res.copy())res.pop()return if root.left :target -= root.valres.append(root.val)self.digui(root.left,target,res,level)target += root.valres.pop()if root.right :target -= root.valres.append(root.val)self.digui(root.right,target,res,level)target += root.valres.pop()return
重点
此题与上一题的不同之处在于:对于返回值的处理上,不需要返回值,设置一个变量,保存所有正确的路径。
代码随想录的代码
递归
class Solution:def __init__(self):self.result = []self.path = []def traversal(self, cur, count):if not cur.left and not cur.right and count == 0: # 遇到了叶子节点且找到了和为sum的路径self.result.append(self.path[:])returnif not cur.left and not cur.right: # 遇到叶子节点而没有找到合适的边,直接返回returnif cur.left: # 左 (空节点不遍历)self.path.append(cur.left.val)count -= cur.left.valself.traversal(cur.left, count) # 递归count += cur.left.val # 回溯self.path.pop() # 回溯if cur.right: # 右 (空节点不遍历)self.path.append(cur.right.val) count -= cur.right.valself.traversal(cur.right, count) # 递归count += cur.right.val # 回溯self.path.pop() # 回溯returndef pathSum(self, root: TreeNode, sum: int) -> List[List[int]]:self.result.clear()self.path.clear()if not root:return self.resultself.path.append(root.val) # 把根节点放进路径self.traversal(root, sum - root.val)return self.result
递归+精简
class Solution:def pathSum(self, root: TreeNode, targetSum: int) -> List[List[int]]:result = []self.traversal(root, targetSum, [], result)return resultdef traversal(self,node, count, path, result):if not node:returnpath.append(node.val)count -= node.valif not node.left and not node.right and count == 0:result.append(list(path))self.traversal(node.left, count, path, result)self.traversal(node.right, count, path, result)path.pop()
迭代:
class Solution:def pathSum(self, root: TreeNode, targetSum: int) -> List[List[int]]:if not root:return []stack = [(root, [root.val])]res = []while stack:node, path = stack.pop()if not node.left and not node.right and sum(path) == targetSum:res.append(path)if node.right:stack.append((node.right, path + [node.right.val]))if node.left:stack.append((node.left, path + [node.left.val]))return res
我的代码(当日晚上自己理解后写)
class Solution:def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:level = []res = []self.digui(root,targetSum,level,res)return resdef digui(self,root,targetSum,level,res):if root == None :return if targetSum - root.val == 0 and root.left == None and root.right == None:level.append(root.val)res.append(level.copy())level.pop()returnif root.left:level.append(root.val)self.digui(root.left,targetSum-root.val,level,res)level.pop()if root.right:level.append(root.val)self.digui(root.right,targetSum-root.val,level,res)level.pop()return
一个改进版,写递归一定要注意的点:return 的编写,什么时候return ,return 之前是否需要回溯等。
106 从中序与后序遍历序列构造二叉树
中序和后序、中序和前序,可以唯一的确定一颗二叉树的前提是:中序和后序(前序),序列里面的值是不重复的,有重复不可能切割。
前序和后序无法确定,因为不知道中间节点。
利用递归构建,利用后序的最后一个节点,来确定根节点,根据根节点来切中序,根据切出的左子树的长度N,取后序中的前N个,即可成功切割后序数组,中序和后序均切割完后,递归构建即可。
本题在切割区间时,要始终注意不变量,采取全部左闭右开。
凡是构造二叉树的题目,采用的遍历顺序均为前序遍历。
重点
涉及非常多的细节!
代码随想录的代码
class Solution:def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:# 第一步: 特殊情况讨论: 树为空. (递归终止条件)if not postorder:return None# 第二步: 后序遍历的最后一个就是当前的中间节点.root_val = postorder[-1]root = TreeNode(root_val)# 第三步: 找切割点.separator_idx = inorder.index(root_val)# 第四步: 切割inorder数组. 得到inorder数组的左,右半边.inorder_left = inorder[:separator_idx]inorder_right = inorder[separator_idx + 1:]# 第五步: 切割postorder数组. 得到postorder数组的左,右半边.# ⭐️ 重点1: 中序数组大小一定跟后序数组大小是相同的.postorder_left = postorder[:len(inorder_left)]postorder_right = postorder[len(inorder_left): len(postorder) - 1]# 第六步: 递归root.left = self.buildTree(inorder_left, postorder_left)root.right = self.buildTree(inorder_right, postorder_right)# 第七步: 返回答案return root
我的代码(当日晚上自己理解后写)
class Solution:def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:if postorder == [] :return Nonemiddle = postorder[-1]node = TreeNode(middle)idx = inorder.index(middle)lefti = inorder[:idx]righti = inorder[idx+1:]leftp = postorder[:idx]rightp = postorder[idx:-1]node.left = self.buildTree(lefti,leftp)node.right = self.buildTree(righti,rightp)return node
105 从前序与中序遍历序列构造二叉树
同上一题,106. 凡是构造二叉树的题目,采用的遍历顺序均为前序遍历。
重点
细节!
代码随想录的代码
class Solution:def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:# 第一步: 特殊情况讨论: 树为空. 或者说是递归终止条件if not preorder:return None# 第二步: 前序遍历的第一个就是当前的中间节点.root_val = preorder[0]root = TreeNode(root_val)# 第三步: 找切割点.separator_idx = inorder.index(root_val)# 第四步: 切割inorder数组. 得到inorder数组的左,右半边.inorder_left = inorder[:separator_idx]inorder_right = inorder[separator_idx + 1:]# 第五步: 切割preorder数组. 得到preorder数组的左,右半边.# ⭐️ 重点1: 中序数组大小一定跟前序数组大小是相同的.preorder_left = preorder[1:1 + len(inorder_left)]preorder_right = preorder[1 + len(inorder_left):]# 第六步: 递归root.left = self.buildTree(preorder_left, inorder_left)root.right = self.buildTree(preorder_right, inorder_right)# 第七步: 返回答案return root
我的代码(当日晚上自己理解后写)
class Solution:def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:if preorder == [] :return Nonemiddle = preorder[0]idx = inorder.index(middle)node = TreeNode(middle)lefti = inorder[:idx]righti = inorder[idx+1:]leftp = preorder[1:idx+1]rightp = preorder[idx+1:]node.left = self.buildTree(leftp,lefti)node.right = self.buildTree(rightp,righti)return node
654 最大二叉树
凡是构造二叉树的题目,采用的遍历顺序均为前序遍历。
本题一眼看上去也是使用递归构建树的题。
重点
本题学习代码随想录解答文章中的优化版本,当 left > right 时,return None
代码随想录的代码
版本一(基础版)
class Solution:def constructMaximumBinaryTree(self, nums: List[int]) -> TreeNode:if len(nums) == 1:return TreeNode(nums[0])node = TreeNode(0)# 找到数组中最大的值和对应的下标maxValue = 0maxValueIndex = 0for i in range(len(nums)):if nums[i] > maxValue:maxValue = nums[i]maxValueIndex = inode.val = maxValue# 最大值所在的下标左区间 构造左子树if maxValueIndex > 0:new_list = nums[:maxValueIndex]node.left = self.constructMaximumBinaryTree(new_list)# 最大值所在的下标右区间 构造右子树if maxValueIndex < len(nums) - 1:new_list = nums[maxValueIndex+1:]node.right = self.constructMaximumBinaryTree(new_list)return node
版本二(使用下标)
class Solution:def traversal(self, nums: List[int], left: int, right: int) -> TreeNode:if left >= right:return NonemaxValueIndex = leftfor i in range(left + 1, right):if nums[i] > nums[maxValueIndex]:maxValueIndex = iroot = TreeNode(nums[maxValueIndex])root.left = self.traversal(nums, left, maxValueIndex)root.right = self.traversal(nums, maxValueIndex + 1, right)return rootdef constructMaximumBinaryTree(self, nums: List[int]) -> TreeNode:return self.traversal(nums, 0, len(nums))
版本三(使用切片)
class Solution:def constructMaximumBinaryTree(self, nums: List[int]) -> TreeNode:if not nums:return Nonemax_val = max(nums)max_index = nums.index(max_val)node = TreeNode(max_val)node.left = self.constructMaximumBinaryTree(nums[:max_index])node.right = self.constructMaximumBinaryTree(nums[max_index+1:])return node
我的代码(当日晚上自己理解后写)
还是切片方便呀。
class Solution:def constructMaximumBinaryTree(self, nums: List[int]) -> Optional[TreeNode]:if nums == [] :return Nonemiddle = max(nums)idx = nums.index(middle)node = TreeNode(middle)lefti = nums[:idx]righti = nums[idx+1:]node.left = self.constructMaximumBinaryTree(lefti)node.right = self.constructMaximumBinaryTree(righti)return node
二叉树系列3总结
代码随想录的总结很不错,直接上链接。
二叉树系列3总结