513.找树左下角的值
问题
题目链接:https://leetcode.cn/problems/find-bottom-left-tree-value/
给定一个二叉树,在树的最后一行找到最左边的值。
示例:
输入: [1,2,3,4,null,5,6,null,null,7]
输出: 7
解答
用层序遍历很简单。
# 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
from collections import deque
class Solution:
def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
result = []
queue = deque([root]) # 注意deque里要输入可迭代对象
while queue:
layer = []
for i in range(len(queue)):
cur = queue.popleft()
layer.append(cur.val)
if cur.left:
queue.append(cur.left)
if cur.right:
queue.append(cur.right)
result.append(layer)
return result[-1][0]
改进一下,不用记录整个树,只返回左下:
from collections import deque
class Solution:
def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
queue = deque([root])
while queue:
size = len(queue)
leftmost = queue[0].val
for i in range(size):
node = queue.popleft()
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
if not queue:
return leftmost
这题用递归较难
我们来分析一下题目:在树的最后一行找到最左边的值。
首先要是最后一行,然后是最左边的值。
如果使用递归法,如何判断是最后一行呢,其实就是深度最大的叶子节点一定是最后一行。
那么如何找最左边的呢?可以使用前序遍历(当然中序,后序都可以,因为本题没有 中间节点的处理逻辑,只要左优先就行),保证优先左边搜索,然后记录深度最大的叶子节点,此时就是树的最后一行最左边的值。
递归三部曲:
1.确定递归函数的参数和返回值
参数必须有要遍历的树的根节点,还有就是一个int型的变量用来记录最长深度。 这里就不需要返回值了,所以递归函数的返回类型为void。
本题还需要类里的两个全局变量,maxLen用来记录最大深度,result记录最大深度最左节点的数值。
2.确定终止条件
当遇到叶子节点的时候,就需要统计一下最大的深度了,所以需要遇到叶子节点来更新最大深度。
3.确定单层递归的逻辑
在找最大深度的时候,递归的过程中依然要使用回溯
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') #这里的值小于0就行
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,因此对上一层递归的depth不会产生影响,因为没有改写depth的值,但传入下一层的是depth+1,实际也进入了下一层递归
self.traversal(node.left, depth+1)
if node.right:
self.traversal(node.right, depth+1)
112. 路径总和 113.路径总和ii
问题
题目链接:https://leetcode.cn/problems/path-sum/submissions/
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。
叶子节点 是指没有子节点的节点。
示例:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如上图所示。
解答
可以采用 257. 二叉树的所有路径 中的方法:
class Solution:
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
if not root: return False
result = []
self.SumPath(root, 0, result)
return (targetSum in result)
def SumPath(self, root, pathsum, result):
pathsum += root.val
if (not root.left) and (not root.right):
result.append(pathsum)
if root.left:
self.SumPath(root.left, pathsum, result)
pathsum -= root.left.val
if root.right:
self.SumPath(root.right, pathsum, result)
pathsum -= root.right.val
(为什么上述不通过)
把pathsum修改为全局变量就能出现正确的结果了:
class Solution:
def __init__(self):
self.pathsum = 0
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
if not root:
return False
result = []
self.SumPath(root, result)
return targetSum in result
def SumPath(self, root, result):
self.pathsum += root.val
if not root.left and not root.right:
result.append(self.pathsum)
if root.left:
self.SumPath(root.left, result)
self.pathsum -= root.left.val
if root.right:
self.SumPath(root.right, result)
self.pathsum -= root.right.val
修改一下就能通过了:
class Solution:
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
if not root: return False
result = []
self.SumPath(root, 0, result)
return (targetSum in result)
def SumPath(self, root, pathsum, result):
pathsum += root.val
if (not root.left) and (not root.right):
result.append(pathsum)
if root.left:
self.SumPath(root.left, pathsum, result)
#pathsum -= root.left.val
if root.right:
self.SumPath(root.right, pathsum, result)
#pathsum -= root.right.val
pathsum -= root.val
改成隐式回溯也能通过了
class Solution:
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
if not root: return False
result = []
self.SumPath(root, root.val, result)
return (targetSum in result)
def SumPath(self, root, pathsum, result):
if (not root.left) and (not root.right):
result.append(pathsum)
if root.left:
self.SumPath(root.left, pathsum+root.left.val, result)
if root.right:
self.SumPath(root.right, pathsum+root.right.val, result)
class Solution:
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
if not root: return False
result = []
self.SumPath(root, [], result)
return (targetSum in result)
def SumPath(self, root, pathsum, result):
pathsum.append(root.val)
if (not root.left) and (not root.right):
result.append(sum(pathsum))
if root.left:
self.SumPath(root.left, pathsum, result)
pathsum.pop()
if root.right:
self.SumPath(root.right, pathsum, result)
pathsum.pop()
精简一下,因为上述方法会遍历整个树,但我们只需要找到一个符合条件的路径就够了:
class Solution:
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
if not root: return False
if not root.left and not root.right and targetSum == root.val:
return True
return self.hasPathSum(root.left, targetSum - root.val) or self.hasPathSum(root.right, targetSum - root.val)
上述代码展开:
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)
113.路径总和ii 是返回所有的路径和满足target,只需要在257. 二叉树的所有路径基础上修改一点就行。
import copy
class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
if not root: return []
result = []
self.Path(root, [], result, targetSum)
return result
def Path(self, root, path, result, targetSum):
path.append(root.val)
if (not root.left) and (not root.right):
if sum(path) == targetSum:
result.append(path)
if root.left:
self.Path(root.left, copy.copy(path), result, targetSum)
if root.right:
self.Path(root.right, copy.copy(path), result, targetSum)
class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
if not root: return []
result = []
self.Path(root, [], result, targetSum)
return result
def Path(self, root, path, result, targetSum):
path.append(root.val)
if (not root.left) and (not root.right):
a = [i for i in path]
if sum(a) == targetSum:
result.append(a)
if root.left:
self.Path(root.left, path, result, targetSum)
path.pop()
if root.right:
self.Path(root.right, path, result, targetSum)
path.pop()
class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
if not root: return []
result = []
self.Path(root, [], result, targetSum)
return result
def Path(self, root, path, result, targetSum):
path.append(root.val)
if (not root.left) and (not root.right):
if sum(path) == targetSum:
result.append(list(path))
if root.left:
self.Path(root.left, path, result, targetSum)
path.pop()
if root.right:
self.Path(root.right, path, result, targetSum)
path.pop()
该题目还有很多问题,之后汇总总结一下(似乎是全局变量和中间变量的问题,但还是有没法解释的结果)
106.从中序与后序遍历序列构造二叉树 105.从前序与中序遍历序列构造二叉树
问题
题目链接:https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/
给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。
示例:
输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]
提示:树的每个元素一定不同。
解答
顺序和我们自己创建树的过程类似:
第一步:如果数组大小为零的话,说明是空节点了。
第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
第五步:切割后序数组,切成后序左数组和后序右数组
第六步:递归处理左区间和右区间
难点在于切割数组时采用的左右区间的开闭。我们在递归的时候要保持这个不改变。
A.首先要切割中序数组,为什么先切割中序数组呢?
切割点在后序数组的最后一个元素,就是用这个元素来切割中序数组的,所以必要先切割中序数组。
中序数组相对比较好切,找到切割点(后序数组的最后一个元素)在中序数组的位置,然后切割
B.接下来就要切割后序数组了。
首先后序数组的最后一个元素指定不能要了,这是切割点 也是 当前二叉树中间节点的元素,已经用了。
后序数组的切割点怎么找?
后序数组没有明确的切割元素来进行左右切割,不像中序数组有明确的切割点,切割点左右分开就可以了。
此时有一个很重的点,就是中序数组大小一定是和后序数组的大小相同的(这是必然)。
中序数组我们都切成了左中序数组和右中序数组了,那么后序数组就可以按照左中序数组的大小来切割,切成左后序数组和右后序数组。
此时,中序数组切成了左中序数组和右中序数组,后序数组切割成左后序数组和右后序数组。
接下来可以递归了。
class Solution:
def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
if not inorder: return None
#在后序找到根节点
root_val = postorder[-1]
root = TreeNode(root_val)
#在中序找到根节点索引
root_index = inorder.index(root_val) #index()函数将返回第一个匹配到的元素的索引位置
#在中序切分左右子树
left_subtree_in = inorder[:root_index]
right_subtree_in = inorder[root_index+1:]
#在后序切分左右子树
#左右子树的长度在中序和后序一定相同
left_subtree_post = postorder[:len(left_subtree_in)]
right_subtree_post = postorder[len(left_subtree_in):-1] #这里的分割区间一定要谨慎
#right_subtree_post = postorder[len(left_subtree_in):len(left_subtree_in)+len(right_subtree_in)]也可以
#递归
root.left = self.buildTree(left_subtree_in, left_subtree_post)
root.right = self.buildTree(right_subtree_in, right_subtree_post)
return root
前序和后序类似,区别是一个从前往后取,一个从后往前取:
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
if not inorder: return None
#在前序找到根节点
root_val = preorder[0]
root = TreeNode(root_val)
#在中序找到根节点索引
root_index = inorder.index(root_val) #index()函数将返回第一个匹配到的元素的索引位置
#在中序切分左右子树
left_subtree_in = inorder[:root_index]
right_subtree_in = inorder[root_index+1:]
#在前序切分左右子树
#左右子树的长度在中序和前序一定相同
left_subtree_pre = preorder[1:len(left_subtree_in)+1]
right_subtree_pre = preorder[len(left_subtree_in)+1:] #这里的分割区间一定要谨慎
#递归
root.left = self.buildTree(left_subtree_pre, left_subtree_in)
root.right = self.buildTree(right_subtree_pre, right_subtree_in)
return root