二叉树系列3
- 二叉树 看到二叉树就想到递归
- 404 左叶子之和
- 重点
- 代码随想录的代码
- 我的代码(当日晚上自己理解后写)
- 513 找树左下角的值
- 重点
- 代码随想录的代码
- 我的代码(当日晚上自己理解后写)
- 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 0
if root.left is None and root.right is None:
return 0
leftValue = self.sumOfLeftLeaves(root.left) # 左
if root.left and not root.left.left and not root.left.right: # 左子树是左叶子的情况
leftValue = root.left.val
rightValue = self.sumOfLeftLeaves(root.right) # 右
sum_val = leftValue + rightValue # 中
return sum_val
迭代法(前序遍历,中左右,思路上很直接,按照定义,统计所有的左孩子即可)
class Solution:
def sumOfLeftLeaves(self, root):
if root is None:
return 0
st = [root]
result = 0
while st:
node = st.pop()
if node.left and node.left.left is None and node.left.right is None:
result += node.left.val
if node.right:
st.append(node.right)
if node.left:
st.append(node.left)
return result
我的代码(当日晚上自己理解后写)
513 找树左下角的值
不会,用层序遍历很简单啊,为什么没想到,用层序遍历!!!
同样,递归法也要学习,这里的递归也要用回溯。
重点
首先是遍历顺序,只要保证,先遍历左节点就可以了,前中后序(中左右,左中右,左右中,左均在右前,所以什么顺序都可以),所以在递归中,用前序遍历。
代码随想录的代码
递归+回溯
class Solution:
def findBottomLeftValue(self, root: TreeNode) -> int:
self.max_depth = float('-inf')
self.result = None
self.traversal(root, 0)
return self.result
def traversal(self, node, depth):
if not node.left and not node.right:
if depth > self.max_depth:
self.max_depth = depth
self.result = node.val
return
if node.left:
depth += 1
self.traversal(node.left, depth)
depth -= 1
if node.right:
depth += 1
self.traversal(node.right, depth)
depth -= 1
递归+隐式回溯
class Solution:
def findBottomLeftValue(self, root: TreeNode) -> int:
self.max_depth = float('-inf')
self.result = None
self.traversal(root, 0)
return self.result
def traversal(self, node, depth):
if not node.left and not node.right:
if depth > self.max_depth:
self.max_depth = depth
self.result = node.val
return
if 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 0
queue = deque()
queue.append(root)
result = 0
while queue:
size = len(queue)
for i in range(size):
node = queue.popleft()
if i == 0:
result = node.val
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return result
我的代码(当日晚上自己理解后写)
112 路径总和
未看讲解,自己编写的青春稚嫩版
class Solution:
def __init__(self):
self.bool = False
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
res = 0
if root == None:
return False
self.digui(root,targetSum,res)
return self.bool
def digui(self,root,target,res):
if not self.bool :
if root.left == None and root.right == None :
res += root.val
if res == target :
self.bool = True
return
else :
return
if root.left :
res += root.val
leftbool = self.digui(root.left,target,res)
res -= root.val
if root.right :
res += root.val
rightbool = self.digui(root.right,target,res)
res -= root.val
return
else :
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: # 遇到叶子节点,并且计数为0
return True
if not cur.left and not cur.right: # 遇到叶子节点直接返回
return False
if cur.left: # 左
count -= cur.left.val
if self.traversal(cur.left, count): # 递归,处理节点
return True
count += cur.left.val # 回溯,撤销处理结果
if cur.right: # 右
count -= cur.right.val
if self.traversal(cur.right, count): # 递归,处理节点
return True
count += cur.right.val # 回溯,撤销处理结果
return False
def hasPathSum(self, root: TreeNode, sum: int) -> bool:
if root is None:
return False
return self.traversal(root, sum - root.val)
递归+精简
class Solution:
def hasPathSum(self, root: TreeNode, sum: int) -> bool:
if not root:
return False
if not root.left and not root.right and sum == root.val:
return True
return 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,那么就返回true
if 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
我的代码(当日晚上自己理解后写)
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.level
def digui(self,root,target,res,level):
if root.left == None and root.right == None :
target -= root.val
if target == 0 :
res.append(root.val)
self.level.append(res.copy())
res.pop()
return
if root.left :
target -= root.val
res.append(root.val)
self.digui(root.left,target,res,self.level)
target += root.val
res.pop()
if root.right :
target -= root.val
res.append(root.val)
self.digui(root.right,target,res,self.level)
target += root.val
res.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 level
def digui(self,root,target,res,level):
if root.left == None and root.right == None :
target -= root.val
if target == 0 :
res.append(root.val)
level.append(res.copy())
res.pop()
return
if root.left :
target -= root.val
res.append(root.val)
self.digui(root.left,target,res,level)
target += root.val
res.pop()
if root.right :
target -= root.val
res.append(root.val)
self.digui(root.right,target,res,level)
target += root.val
res.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[:])
return
if not cur.left and not cur.right: # 遇到叶子节点而没有找到合适的边,直接返回
return
if cur.left: # 左 (空节点不遍历)
self.path.append(cur.left.val)
count -= cur.left.val
self.traversal(cur.left, count) # 递归
count += cur.left.val # 回溯
self.path.pop() # 回溯
if cur.right: # 右 (空节点不遍历)
self.path.append(cur.right.val)
count -= cur.right.val
self.traversal(cur.right, count) # 递归
count += cur.right.val # 回溯
self.path.pop() # 回溯
return
def pathSum(self, root: TreeNode, sum: int) -> List[List[int]]:
self.result.clear()
self.path.clear()
if not root:
return self.result
self.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 result
def traversal(self,node, count, path, result):
if not node:
return
path.append(node.val)
count -= node.val
if 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
我的代码(当日晚上自己理解后写)
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
我的代码(当日晚上自己理解后写)
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
我的代码(当日晚上自己理解后写)
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 = 0
maxValueIndex = 0
for i in range(len(nums)):
if nums[i] > maxValue:
maxValue = nums[i]
maxValueIndex = i
node.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 None
maxValueIndex = left
for i in range(left + 1, right):
if nums[i] > nums[maxValueIndex]:
maxValueIndex = i
root = TreeNode(nums[maxValueIndex])
root.left = self.traversal(nums, left, maxValueIndex)
root.right = self.traversal(nums, maxValueIndex + 1, right)
return root
def 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 None
max_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
我的代码(当日晚上自己理解后写)
二叉树系列3总结
代码随想录的总结很不错,直接上链接。
二叉树系列3总结