「这是我参与11月更文挑战的第 22 天,活动详情查看:2021最后一次更文挑战」
刷算法题,从来不是为了记题,而是练习把实际的问题抽象成具体的数据结构或算法模型,然后利用对应的数据结构或算法模型来进行解题。个人觉得,带着这种思维刷题,不仅能解决面试问题,也能更多的学会在日常工作中思考,如何将实际的场景抽象成相应的算法模型,从而提高代码的质量和性能
二叉树中和为某一值的路径(二)
题目描述
给你二叉树的根节点 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]]
示例 2
输入:root = [1,2,3], targetSum = 5
输出:[]
示例 3
输入:root = [1,2], targetSum = 0
输出:[]
提示:
- 树中节点总数在范围
[0, 5000]内 1000 <= Node.val <= 10001000 <= targetSum <= 1000
解题
解法一:深度优先搜索
思路
这道题和前边的求【根节点到叶子结点的数字之和】差不多,思路一样。其实核心就是,我们需要遍历每一条从根节点到叶子节点的路径。这就很容易想到深度优先了
深度优先搜索:随意选择一个岔路口来走,走着走着发现走不通的时候,就回退到上一个岔路口,重新选择一条路继续走,直到走完所有的情况
要求跟结点到叶子节点的路径和,跟求跟根节点的子节点到叶子节点的路径和,计算方式是一样的,因此就可以用递归了。遍历过程中,记录每个结点的值,并且累加路径和
- 遍历的过程中,如果该节点的左右子节点均为空,且路径和为目标值,则这条路径是有效的
- 如果左右节点不为空,继续遍历
一看代码就明白
代码
//深度优先搜索
func pathSum(root *TreeNode, targetSum int) [][]int {
var res [][]int
eachPath := []int{}
var dfsPathSum func(root *TreeNode, last int)
dfsPathSum = func(root *TreeNode, last int) {
if root == nil {
return
}
last -= root.Val
eachPath = append(eachPath, root.Val)
defer func() {
eachPath = eachPath[:len(eachPath)-1] //一条路径走完之后,回退到上一步的路径,继续往下走,所以需要去掉路径中的最后一个
}()
if root.Left == nil && root.Right == nil && last == 0 {
res = append(res, append([]int(nil), eachPath...))
return
}
dfsPathSum(root.Left, last)
dfsPathSum(root.Right, last)
}
dfsPathSum(root, targetSum)
return res
}
解法二:广度优先搜索
思路
前边已经遇到很多树的题,都用到了深度优先搜索,和广度优先搜索,也说了用深度优先搜索能解的题,基本上都是可以用广度优先搜索解决(反之亦然)
广度优先搜索:是一种“地毯式”层层推进的搜索策略,即先查找离起始顶点最近的,然后是次近的,依次往外搜索
深度优先搜索在树上的提现本质上就是一种层序遍历。那本题用广度优先搜索的难点就是,如果回推出路径,因为广度优先并不会一路搜索到底,是一层一层往下搜索。因此,当我们搜索到左右子节点都为空,且得到了我们的目标值,就应该能够根据当前结点,往回倒退到跟结点,这样就能得到路径了
因此,需要我们在搜索的过程中,记录每个结点的父节点,方便后边推路径。有了这个思路,代码就好写了
加句废话:你可能刷了十几甚至二十几到树的题,遇到变形的树的题还是不会做(比如我),挺挫败的。真的,别慌,刷下去,一定要总结,当把一些解法类型的题归类总结之后,相信一定可以掌握的(我目前在分类的刷树的题,大概这两周刷的差不多,然后开始对前边树的题进行详细总结)
不会就看答案,敲不出来就多敲几便,没有一道题是敲一遍就永远记住的。思路和思考过程的重要性,远大于结果
代码
//广度优先搜索
type pairs struct {
node *TreeNode
left int
}
func pathSum2(root *TreeNode, targetSum int) (res [][]int) {
if root == nil {
return [][]int{}
}
parent := map[*TreeNode]*TreeNode{} //用于记录每个节点的父节点
//通过叶子结点,回推到根节点,记录路径
getPath := func(node *TreeNode) (path []int) {
for ;node != nil; node = parent[node] {
path = append(path, node.Val)
} //这个路径是叶子结点到根节点的,所以需要逆序一下
for i, j := 0, len(path)-1;i < j; i++ {
path[i], path[j] = path[j], path[i]
j--
}
return
}
//常规的广度优先搜索思路,借助队列来记录每一层的结点
bfsQueue := []pairs{{root, targetSum}}
for len(bfsQueue) != 0 {
pair := bfsQueue[0]
bfsQueue = bfsQueue[1:]
node := pair.node
left := pair.left - node.Val
if node.Left == nil && node.Right == nil {
if left == 0 {
res = append(res, getPath(node))
}
} else {
if node.Left != nil {
parent[node.Left] = node
bfsQueue = append(bfsQueue, pairs{node.Left, left})
}
if node.Right != nil {
parent[node.Right] = node
bfsQueue = append(bfsQueue, pairs{node.Right, left})
}
}
}
return res
}