LeetCode 513.找树左下角的值:
思路:
需要明确的是,树左下角的值为最底层最左边节点的值,而不是最左边节点的值
- 迭代
利用层序遍历很容易可以解决,只要在对每层的节点进行处理时保存第一个当前层第一个节点的值即可
from collections import deque
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
queue = deque() # 记左边为队首,右边为队尾
if root: queue.append(root)
result = 0
while queue:
result = queue[0].val # 当前层的最左边结点的值
q_size = len(queue)
for _ in range(q_size):
cur = queue.popleft()
if cur.left: queue.append(cur.left)
if cur.right: queue.append(cur.right)
return result
- 递归
因为需要找树最底层最左边节点的值,因此遍历时应当左节点在右节点前。以采用前序遍历的方式为例
① 传入参数和返回参数:需要传入节点node和节点深度depth,不需要返回值,但是需要全局变量maxdepth和result记录深度最大最左边节点的深度和值
self.maxdepth = -1 # 记录根节点深度为0,因此maxdepth初始化为-1
self.result = 0
def traversal(node, depth)
② 边界条件:遇到叶子节点
if not node.left and not node.right:
if depth > self.maxdepth:
self.maxdepth = depth
self.result = node.val
return # 容易遗忘
③ 正常递归下去:回溯,首先递归左子树,接着递归右子树
if node.left:
depth += 1
self.traversal(node.left, depth)
depth -= 1 # 回溯
# 上面也可以直接换成如下
# self.traversal(node.left, depth + 1) # 隐形回溯
if node.right:
depth += 1
self.traversal(node.right, depht)
depth -= 1
# or
# self.traversal(node.right, depth + 1)
class Solution:
def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
self.maxdepth = -1 # 根节点深度为0,因此maxdepth初始化为-1
self.result = 0
self.traversal(root, 0)
return self.result
def traversal(self, node, depth):
if node.left is None and node.right is None:
if depth > self.maxdepth:
self.maxdepth, self.result = depth, 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 # 回溯
感悟:
边界条件的return容易遗忘
路径总和:
LeetCode 112.路径总和:
思路:
对二叉树进行深度优先遍历,同时记录根节点到叶子节点的路径和,判断是否有符合条件的路径
- 递归
① 传入参数和返回参数:
传入节点node,计数器count;因为只需要判断是否存在,因此需要返回值,函数返回True / False(tips: count从targetSum开始进行递减,最终判断是否为0会更好用)
bool tarversal(TreeNode node, int count)
② 边界条件:叶节点,同时对count进行判断
if node.left == NULL && node.right == NULL
if count == 0 return True
else return False
③ 正常递归:用到了回溯
if node.left != NULL{
left_flag = traversal(node.left, count - node.left.val) # 隐形回溯
if left_flag return True
}
if node.right != NULL{
right_flag = traversal(node.right, count - node.right.val)
if right_flag return True
}
return False
- 使用栈模拟深度优先遍历
因为需要根节点到叶子节点的路径加和,因此栈中的元素应当为(node, count),其中count记录从根节点到node节点的路径加和
class Solution:
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
if not root:
return False
stack = [(root, root.val)]
while stack:
cur, count = stack.pop()
# 当前节点为叶节点且路径和为targetSum
if not cur.left and not cur.right and count == targetSum:
return True
if cur.right:
stack.append((cur.right, count + cur.right.val))
if cur.left:
stack.append((cur.left, count + cur.left.val))
return False
LeetCode 113.路径总和Ⅱ:
思路:
- 递归
递归的思路与上题相同,不同之处传入参数需要增加path来记录路径,同时不需要返回值(因为path已经记录了路径,而全部路径由全局变量记录)
需要注意的是:全局变量记录path时,添加的是path[:],即path的复印件,直接添加path的话,添加的是path的引用
class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
if not root:
return []
self.result = []
self.traversal(root, targetSum - root.val, [root.val])
return self.result
def traversal(self, node, count, path):
if not node.left and not node.right:
if count == 0:
self.result.append(path[:]) # 添加path的复印件
return # 无返回值
if node.left:
path.append(node.left.val)
self.traversal(node.left, count - node.left.val, path) # 隐形回溯
path.pop() # 回溯
if node.right:
path.append(node.right.val)
self.traversal(node.right, count - node.right.val, path) # 隐形回溯
path.pop() # 回溯
- 迭代
得益于python中模拟栈可以用列表,而列表可以添加任何元素且不限制元素的大小,因此直接采用上一题的思路,栈中的元素为(node, path, count),path记录从根节点到node的路径
class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
if not root:
return []
stack = [(root, [root.val], root.val)]
result = []
while stack:
cur, path, count = stack.pop()
if not cur.left and not cur.right and count == targetSum:
result.append(path)
if cur.right: stack.append((cur.right, path + [cur.right.val], count + cur.right.val))
if cur.left: stack.append((cur.left, path + [cur.left.val], count + cur.left.val))
return result
感悟:
使用回溯时需要注意什么时候回溯,以及累计参数count与当前节点值node.val之间,是在上一次递归时被修改还是这一次递归时被修改。
深度优先遍历的栈模拟迭代。
从中序与后序遍历序列构造二叉树:
LeetCode 106.从中序与后序遍历序列构造二叉树
思路:
首先根据后序序列的最后一个找到根节点,再根据根节点切分中序序列,最后根据左子树中序序列数量和后序序列数量相等切分后序序列。
需要注意的是:一定要注意切分时选择的区间是左闭右开还是双闭区间。比如解法1中对列表进行切片,如leftInorder = inorder[:i],截取时就是左闭右开区间。比如解法二中使用下标索引,就是双闭区间
- 根据传入的中序和后序,切片构造新的中序和后序
class Solution:
def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
if len(inorder) == 0 or len(postorder) == 0: # 不是中序序列/后序序列
return None
return self.traversal(inorder, postorder)
def traversal(self, inorder, postorder):
len_in = len(inorder)
if len_in == 0: # 空序列对应空节点(什么时候是空序列)
return None
# 构造根节点
nodeValue = postorder[-1]
node = TreeNode(val=nodeValue)
if len_in == 1: # 是叶子节点 (什么时候是叶子节点)
return node
# # 定位根节点在中序序列中的位置
# for i in range(len_in):
# if inorder[i] == nodeValue:
# break
i = inorder.index(nodeValue)
"""
注意此时是左闭右开区间
"""
# 对中序进行切分
left_inorder = inorder[:i] # 列表切片默认是左闭右开,实际上得到的left_inorder不包含node节点
right_inorder = inorder[i + 1:]
"""
注意此时是左闭右开区间,right_postorder容易弄错
"""
# 左子树的中序序列的数量和后序序列相同,对后序进行切分
left_postorder = postorder[:i] # 元素个数为i个
right_postorder = postorder[i: -1] # postorder[i]是右子树的一个节点,左闭右开区间
# 构造并连接左右子树和根节点
node.left = self.traversal(left_inorder, left_postorder)
node.right = self.traversal(right_inorder, right_postorder)
return node
- 使用列表索引
注意此时为双闭区间,空区间为start > end, 只有一个元素的区间为start == end。
需要注意的是:使用list.index()定位时,使用的是整个inorder而不是inorder[in_start, in_end],第二个的话返回是新列表inorder[in_start, in_end]中根节点的位置,是从0开始的,而不是我们想要的从in_start开始的索引
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
len_inorder = len(inorder)
if len_inorder == 0 or len(postorder) == 0:
return None
root = self.traversal(inorder, 0, len_inorder - 1, postorder, 0, len_inorder - 1)
return root
# 是一个双闭区间
def traversal(self, inorder, in_start, in_end, postorder, post_start, post_end):
"""
这里出错过,空区间与只有一个元素的区间
"""
if in_start > in_end:
return None # 空列表对应空结点
# 构造根节点
node = TreeNode(val=postorder[post_end])
# 如果是叶子节点
if in_end == in_start:
# print("\n")
return node
# # 定位根节点在中序中的位置
# for i in range(in_start, in_end + 1):
# if inorder[i] == postorder[post_end]:
# break
"""
注意这里定位使用的是整个inorder而不是inorder[in_start, in_end],第二个的话返回是新列表inorder[in_start, in_end]中根节点的位置,是从0开始的
"""
i = inorder.index(postorder[post_end])
# 切分中序
left_in_start = in_start
left_in_end = i - 1
right_in_start = i + 1
right_in_end = in_end
# 切分后序
left_post_start = post_start
left_post_end = left_in_end - left_in_start + left_post_start # 元素个数相等
right_post_start = left_post_end + 1
right_post_end = post_end - 1
# 构造并连接左右子树到根节点
node.left = self.traversal(inorder, left_in_start, left_in_end, postorder, left_post_start, left_post_end)
node.right = self.traversal(inorder, right_in_start, right_in_end, postorder, right_post_start, right_post_end)
return node
LeetCode 105.从前序与中序遍历序列构造二叉树
思路:
思路与上一题相似,只是从后序变为了前序
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
if len(preorder) == 0 or len(inorder) == 0:
return None
root = self.traversal(preorder, inorder)
return root
def traversal(self, preorder, inorder):
lenOrder = len(preorder)
if lenOrder == 0: # 空区间
return None
node = TreeNode(val=preorder[0])
if lenOrder == 1: # 只有一个元素,叶子节点
return node
# 定位
indexNode = inorder.index(preorder[0])
# 划分中序
leftInorder = inorder[:indexNode]
rightInorder = inorder[indexNode + 1:]
# 划分前序
leftPreorder = preorder[1: indexNode + 1]
rightPreorder = preorder[indexNode + 1:]
node.left = self.traversal(leftPreorder, leftInorder)
node.right = self.traversal(rightPreorder, rightInorder)
return node
感悟:
区间开闭,list.index的具体实现对代码的影响
学习收获:
回溯,以及使用栈来模拟类似回溯二叉树的深度优先搜索(入栈元素改变),使用中序和后序/前序构造二叉树注意区间