开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情
前言
提到树的回溯算法,就不得不提树的深度优先遍历。说到树的深度优先遍历,就离不开讲树的前、中、后序遍历方法。
我们浅浅回顾一下二叉树的三种遍历方法:
- 前序遍历:根左右。先打印根节点,再遍历左子树,再遍历右子树
- 中序遍历:左根右。先遍历左子树,再打印根节点,再遍历右子树
- 后序遍历:左右根。先遍历左子树,再遍历右子树,再打印根节点
type TreeNode struct{
Val int
Left *TreeNode
Right *TreeNode
}
// 前序遍历
func preOrder(root *TreeNode) (res []int) {
if root == nil {
return
}
res = append(res, root.Val)
res = append(res, preOrder(root.Left)...)
res = append(res, preOrder(root.Right)...)
return res
}
// 中序遍历
func MidOrder(root *TreeNode) (res []int) {
if root == nil {
return
}
res = append(res, MidOrder(root.Left)...)
res = append(res, root.Val)
res = append(res, MidOrder(root.Right)...)
return res
}
// 后序遍历
func PostOrder(root *TreeNode) (res []int) {
if root == nil {
return
}
res = append(res, PostOrder(root.Left)...)
res = append(res, PostOrder(root.Right)...)
res = append(res, root.Val)
return res
}
深度优先遍历和回溯法其主要的区别是:回溯法在求解过程中不保留完整的树结构,而深度优先搜索则记下完整的搜索树
正文
初学者可能会纳闷,回溯到底是什么?下面这张图很好的模拟了回溯的实现步骤(图片来自:代码随想录)。从根节点出发,一直向下直到叶子结点,然后回溯到其父节点,继续向下到另一个叶子结点...这是一个递归的过程
只靠脑补过程不能很好的理解回溯的实现,我们直接在实战中加强理解
根据题目,显然我们要使用到二叉树的前序遍历以及回溯的思想,话不多说,我们直接上码
var path = []int{} //用来接收路径上的值
func pathSum(root *TreeNode, target int) [][]int {
if root == nil {
return [][]int{}
}
temp := [][]int{} //我们最终要返回满足条件的路径集
traveral(root, &temp, target)
return temp
}
func traveral(root *TreeNode, temp *[][]int, target int) {
path = append(path, root.Val)
//当递归到叶子结点时,进行判断
if root.Left == nil && root.Right == nil {
if target - root.Val == 0 {
tmp := make([]int, len(path))
copy(tmp, path) //这里注意!!!我们不能直接将path加入到temp中,因为后序回溯时还要对path进行修改,会修改掉path原来的值,因此应该使用深拷贝将path的值加入temp
*temp = append(*temp, tmp)
}
return
}
if root.Left != nil {
traveral(root.Left, temp, target-root.Val)
path = path[:len(path)-1] //回溯的实现
}
if root.Right != nil {
traveral(root.Right, temp, target-root.Val)
path = path[:len(path)-1] //回溯的实现,回溯到上一个节点位置
}
}