导语
leetcode刷题笔记记录,本篇博客记录二叉树部分的题目,主要题目包括:
- 513 找树左下角的值
- 112 路径总和
- 113 路径总和ii
- 106 从中序与后序遍历序列构造二叉树
- 105 从前序与中序遍历序列构造二叉树
Leetcode 513 找树左下角的值
题目描述
给定一个二叉树的 根节点 root
,请找出该二叉树的 最底层 最左边 节点的值。假设二叉树中至少有一个节点。
示例 1:
输入: root = [2,1,3]
输出: 1
解法
这道题目使用层次遍历最简单直观,只需要返回最后一层的第一个值即可,代码如下:
from collections import deque
class Solution:
def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
queue = deque()
if root:
queue.append(root)
result = root.val
while queue:
size = len(queue)
items = []
for _ in range(size):
node = queue.popleft()
items.append(node.val)
if node.left: queue.append(node.left)
if node.right: queue.append(node.right)
result = items[0]
return result
同时,也可以使用递归来求解,在进行递归时,仍需要使用回溯的思想,由于不需要对中间节点进行任何操作,所以任意一种递归的遍历顺序均可以。
- 递归函数的参数和返回值
参数必须有要遍历的树的根节点,还有就是一个int型的变量用来记录最长深度。不需要返回值。本题还需要类里的两个全局变量,max_depth用来记录最大深度,result记录第一个到达最大深度的节点(就是我们要找的这个节点)的数值。
- 终止条件
当遇到叶子节点的时候,就需要统计一下最大的深度了,所以需要遇到叶子节点来更新最大深度。
- 单层递归的逻辑
在找最大深度的时候,递归的过程中依然要使用回溯
class Solution:
def findBottomLeftValue(self, root: Optional[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
Leetcode 112/113 路径总和i、ii
题目描述
给你二叉树的根节点 root
和一个表示目标和的整数 targetSum
。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum
。如果存在,返回 true
;否则,返回 false
。
叶子节点 是指没有子节点的节点。
示例 1:
输入: root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出: true
解释: 等于目标和的根节点到叶节点路径如上图所示。
解法
使用递归和回溯进行求解,由于这里只需要判断是否存在,所以需要返回一个布尔值。对于递归函数什么时候需要返回值?什么时候不需要返回值?代码随想录总结如下三点:
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(这种情况就是本文下半部分介绍的113.路径总和ii)
- 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (这种情况我们在236. 二叉树的最近公共祖先 (opens new window)中介绍)
- 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况)
所以本题目的递归三部曲如下:
- 递归函数的参数和返回类型:需要一个树的根节点和一个计数器用于计算目标和,返回值为布尔值;
- 终止条件:让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。如果遍历到了叶子节点,count不为0,就是没找到。
- 单层逻辑:不判断空节点,如果递归函数返回了True则可以直接返回,表明找到了。
class Solution:
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
if not root:
return False
return self.traversal(root, targetSum-root.val)
def traversal(self, node, count):
if node.left == None and node.right == None:
return count == 0
if node.left:
count -= node.left.val
if self.traversal(node.left, count):
return True
count += node.left.val
if node.right:
count -= node.right.val
if self.traversal(node.right, count):
return True
count += node.right.val
return False
113 路径总和ii
题目描述
给你二叉树的根节点 root
和一个整数目标和 targetSum
,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。叶子节点 是指没有子节点的节点。
示例 1:
输入: root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出: [[5,4,11,2],[5,8,4,5]]
解法
与112类似,只不过这里更加复杂,需要保留路径,因此定义两个全局变量用于存储当前的路径和所有符合条件的结果。路径总和ii要遍历整个树,找到所有路径,所以递归函数不要返回值!
class Solution:
def __init__(self):
self.result = []
self.path = []
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
self.result.clear()
self.path.clear()
if root is None:
return self.result
self.path.append(root.val)
self.traversal(root, targetSum - root.val)
return self.result
def traversal(self, cur, count):
if cur.left is None and cur.right is None:
if count == 0:
self.result.append(self.path[:])
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
Leetcode 106/105 构造二叉树
题目描述
给定两个整数数组 inorder
和 postorder
,其中 inorder
是二叉树的中序遍历, postorder
是同一棵树的后序遍历,请你构造并返回这棵 二叉树 。
示例 1:
输入: inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出: [3,9,20,null,null,15,7]
提示:
1 <= inorder.length <= 3000
postorder.length == inorder.length
-3000 <= inorder[i], postorder[i] <= 3000
inorder
和postorder
都由 不同 的值组成postorder
中每一个值都在inorder
中inorder
保证是树的中序遍历postorder
保证是树的后序遍历
解法
根据两个顺序构造一个唯一的二叉树,以后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来再切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。流程如图(参考代码随想录):
递归逻辑如下列代码段所示:
class Solution:
def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
# 第一步:特殊情况,处理空的情况
if not inorder:
return None
# 第二步:后序遍历的最后一个节点就是根节点
root_value = postorder[-1]
root = TreeNode(root_value)
# 第三步:寻找切割点
sep_index = inorder.index(root_value)
# 第四步:切割中序数组,得到左中序、右中序
left_inorder = inorder[:sep_index]
right_inorder = inorder[sep_index+1:]
# 第五步:切割postorder数组,得到左后序、右后序
# 重点:中序数组大小一定与后序数组大小相同的
left_postorder = postorder[:len(left_inorder)]
right_postorder = postorder[len(left_inorder):len(postorder)-1]
# 第六步:递归
root.left = self.buildTree(left_inorder, left_postorder)
root.right = self.buildTree(right_inorder, right_postorder)
# 第七步:返回答案
return root
类似的,我们可以得到根据前序和中序的序列获得二叉树的方法,具体代码如下:
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
# 第一步:特殊情况,处理空的情况
if not inorder:
return None
# 第二步:后序遍历的最后一个节点就是根节点
root_value = preorder[0]
root = TreeNode(root_value)
# 第三步:寻找切割点
sep_index = inorder.index(root_value)
# 第四步:切割中序数组,得到左中序、右中序
left_inorder = inorder[:sep_index]
right_inorder = inorder[sep_index+1:]
# 第五步:切割postorder数组,得到左后序、右后序
# 重点:中序数组大小一定与后序数组大小相同的
left_preorder = preorder[1:len(left_inorder)+1]
right_preorder = preorder[len(left_inorder)+1:]
# 第六步:递归
root.left = self.buildTree(left_preorder, left_inorder)
root.right = self.buildTree(right_preorder, right_inorder)
# 第七步:返回答案
return root