代码随想录算法训练营第十四天(二叉树篇)|Leetcode513找树左下角的值,Leetcode112路径总和,Leetcode106从中序和后序遍历构造二叉树
Leetcode 513 找树左下角的值
题目链接: 找树左下角的值
给定一棵二叉树的根节点,返回该二叉树中最后一行最靠左的节点的值。
思路: 本题既可以通过迭代求解,也可以通过递归求解。
若通过迭代法求解,则直接通过层序遍历,遍历整棵二叉树。最后将最后一行的第一个元素取出即可。
若使用递归法求解,则需要注意递归顺序。此题中我们采用前序遍历,遍历整棵二叉树,同时我们维护一个最大深度变量。若当前节点深度大于最大深度时,更新最大深度的同时记录该节点信息。由于我们采用的是前序遍历(中左右),当深度突破最大深度时,记录的相应节点信息一定是最靠左的节点。
利用递归三部曲进行分析:
- 递归函数参数包括当前正在遍历的节点和当前节点的深度; 返回值为空,因为我们采用一个全局变量记录节点最大深度
- 当前节点为空时,终止递归操作
- 每层递归操作中,首先比较当前节点深度与最大深度,据此更新最左子节点。随后,递归地处理左右孩子节点
具体代码实现
# 迭代法
# 通过层序遍历,取最后一层的最左侧节点,直接返回即可
class Solution:
def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
from collections import deque
result = []
if root is None:
return 0
myQueue = deque()
myQueue.append(root)
while myQueue:
size = len(myQueue)
floorResult = []
for _ in range(size):
node = myQueue.popleft()
floorResult.append(node.val)
if node.left:
myQueue.append(node.left)
if node.right:
myQueue.append(node.right)
result.append(floorResult)
return result[-1][0]
# 递归法
class Solution:
maxDepth = 0
result = 0
def traverse(self, cur: Optional[TreeNode], depth: int) -> None:
if cur is None:
return
# 更新最大深度
if depth > self.maxDepth:
self.maxDepth = depth
self.result = cur.val
# 递归处理左右节点。
# 需要特别注意的是在递归过程中包含深度的回溯,而回溯的过程包含在参数中。
# 此处传递的是 `depth + 1` 而非 `depth`
# 此操作与 depth += 1, self.traverse(cur.left, depth), depth -=1 等价
self.traverse(cur.left, depth + 1)
self.traverse(cur.right, depth + 1)
def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
self.traverse(root, 1)
return self.result
Leetcode 112 路径总和
题目链接: 路径总和
给定一棵二叉树的根节点以及一个整数。判断该二叉树中是否存在一条从根节点到叶节点的路径,使得该路径上所有节点值相加等于给定的整数。
思路: 此题涉及到路径,需要我们进行回溯操作。
利用递归三部曲进行分析:
- 递归函数的参数与返回值
递归函数参数为当前正在遍历的节点,沿着当前路径已经累加得到的和以及目标和; 返回值为布尔值,含义为是否存在一条路径符合要求。只要有一条路径符合要求,则说明当前二叉树符合要求。 - 递归终止条件
当遍历到空节点时,停止向下递归,比较沿着当前路径的和与目标和是否相等,若相等,则返回True,否则返回False - 每层递归所做操作
每层递归时将自己的左右孩子节点加入到路径中(注意回溯)
具体代码实现
class Solution:
def getPath(self, cur: Optional[TreeNode], sum: int, targetSum: int) -> bool:
# 当前节点为叶子节点,构成了一条完整的路径
# 判断该路径总和是否与目标和相等
if cur.left is None and cur.right is None:
if sum == targetSum:
return True
return False
# 汇总包含左子节点的路径的情况和包含右子节点路径的情况
# 注意回溯过程隐藏在参数传递的过程中
if cur.left and cur.right:
return self.getPath(cur.left, sum+cur.left.val, targetSum) or self.getPath(cur.right, sum+cur.right.val, targetSum)
# 仅存在左子节点
if cur.left:
return self.getPath(cur.left, sum+cur.left.val, targetSum)
# 仅存在右子节点
if cur.right:
return self.getPath(cur.right, sum+cur.right.val, targetSum)
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
if root is None:
return False
return self.getPath(root, root.val, targetSum)
Leetcode 106 从中序遍历和后续遍历构造二叉树
题目链接: 从中序遍历和后续遍历构造二叉树
给定中序遍历数组和后序遍历数组,需要据此构造一个相应的二叉树,并返回相应的根节点。给定的数组中确保不存在值相同的元素
Example1:
inorder = [9,3,15,20,7]
postorder = [9,15,7,20,3]
能够唯一确定以下二叉树:
3
/ \
9 20
/ \
15 7
思路: 首先回忆中序遍历和后序遍历的特质。中序遍历通过左中右的方式进行遍历操作,后序遍历通过左右中的方式进行遍历操作。
我们首先根据后序遍历数组确定二叉树根节点,再以此划分中序数组,将其划分为左子树和右子树两个部分。由于中序遍历和后序遍历对应的元素个数相同。因此我们可以通过中序数组划分后两个部分的长度,确定新的后序数组。重复以上操作,直到完成所有节点的构建。
举例来说,整个过程如下:
inorder = [9,3,15,20,7]
postorder = [9,15,7,20,3]
根据后序数组,确定二叉树根节点为 3
,以 3
为基准,划分中序数组。中序数组被划分为了两部分: [9]
, [15,20,7]
,这两部分的长度分别为 1和3。再据此划分后序数组,同样划分为两部分: [9]
, [15,7,20]
。以此类推,重复以上步骤。
利用递归三部曲进行分析:
- 递归函数参数与返回值:
递归函数参数应当为中序遍历数组和后序遍历数组,返回值应当为创建好的二叉树相应节点。 - 递归终止条件:
当后序遍历数组为空时,递归结束,返回空节点。 - 每层递归所做的操作:
根据后序遍历确定根节点,根据根节点在中序数组中的下标分割中序数组,随后再分割后序数组
具体代码实现
根据分割数组实现方式的不同,可以分为复制数组片段和传递原数组以及相应下标两种实现方式
class Solution:
def build(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
# 后序数组为空,终止递归操作
if not postorder:
return
rootVal = postorder.pop()
rootNode = TreeNode(rootVal)
# 找出该元素在中序数组中的下标
for i, num in enumerate(inorder):
if num == rootVal:
index = i
break
# 创建新数组对象,占用更多内存资源
leftInorder = inorder[:index]
rightInorder = inorder[index+1:]
leftPostorder = postorder[:index]
rightPostorder = postorder[index:]
rootNode.left = self.build(leftInorder, leftPostorder)
rootNode.right = self.build(rightInorder, rightPostorder)
return rootNode
def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
root = self.build(inorder, postorder)
return root
class Solution:
def build(self, inorder: List[int], postorder: List[int], inorderStartIdx: int, inorderEndIdx: int, postorderStartIdx: int, postorderEndIdx: int) -> Optional[TreeNode]:
if postorderStartIdx >= postorderEndIdx:
return None
rootVal = postorder[postorderEndIdx-1]
rootNode = TreeNode(rootVal)
for i, num in enumerate(inorder):
if num == rootVal:
index = i
break
leftInorderStartIdx = inorderStartIdx
leftInorderEndIdx = index
rightInorderStartIdx = index + 1
rightInorderEndIdx = inorderEndIdx
leftPostorderStartIdx = postorderStartIdx
leftPostorderEndIdx = postorderStartIdx + index - inorderStartIdx
rightPostorderStartIdx = postorderStartIdx + index - inorderStartIdx
rightPostorderEndIdx = postorderEndIdx - 1
# 传递相应数组下标,这样递归函数中不需要再创建新的数组,节约了内存空间
rootNode.left = self.build(inorder, postorder, leftInorderStartIdx, leftInorderEndIdx, leftPostorderStartIdx, leftPostorderEndIdx)
rootNode.right = self.build(inorder, postorder, rightInorderStartIdx, rightInorderEndIdx, rightPostorderStartIdx, rightPostorderEndIdx)
return rootNode
def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
# 坚持在划分区间时采用左闭右开区间
root = self.build(inorder, postorder, 0, len(inorder), 0, len(postorder))
return root