回溯需要考虑的三个问题
-
路径
-
选择列表: 每一层for循环是下一层要去展开的列表
-
结束条件
-
重复/复选
框架
visited = map[int]boool{}
result = []int {}
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
剪枝
做选择
标记visited[]
backtrack(路径, 选择列表)
撤销选择
取消标记vistied[]
46. 全排列
无重()不可复选(visited)
最经典的回溯,用长度来控制递归深度。穷举所有可能,用全局visited[]来记录当前的这条分支上节点的使用情况,避免重复使用,visited[]充当了剪枝的效果
func permute(nums []int) [][]int {
res := [][]int{}
visited := map[int]bool {}
dfs([]int {}, nums, &res, visited)
return res
}
func dfs(path []int,nums []int, res *[][]int, visited map[int]bool) {
// 满足条件以后,加到结果当中
if len(path) == len(nums) {
// 做参数传递的时候,保存当前结果值的话,需要copy.. 切片传递虽然是值传递。但是会共用底层的数组指针
temp := make([]int,len(nums))
copy(temp, path)
*res = append(*res, temp)
}
for _, n := range nums {
// 剪枝--for循环少展开一次,就少一个枝
if visited[n] {
continue
}
// 吞进来
path = append(path, n)
visited[n] = true
dfs(path, nums, res, visited)
// 吐出去
path = path[:len(path)-1]
visited[n] = false
}
}
func permute(nums []int) [][]int {
res := [][]int{}
visited := map[int]bool{}
var dfs func(path []int)
dfs = func(path []int) {
if len(path) == len(nums) {
temp := make([]int, len(path))
copy(temp, path)
res = append(res, temp)
return
}
for _, n := range nums {
if visited[n] {
continue
}
path = append(path, n)
visited[n] = true
dfs(path)
path = path[:len(path)-1]
visited[n] = false
}
}
dfs([]int{})
return res
}
78. 子集
元素无重不可复选
- copy
- for - start 不重复
func subsets(nums []int) [][]int {
var path []int
var res [][]int
var dfs func(start int)
dfs = func (start int) {
// 不用return
// temp得指定长度,才能达到copy效果
temp := make([]int,len(path))
copy(temp, path)
res = append(res, temp)
// for循环自带结束
for i := start; i < len(nums); i ++ {
path = append(path, nums[i])
dfs (i+1)
path = path[:len(path) - 1]
}
}
dfs(0)
return res
}
90. 子集 II
元素可重(排序和剪枝)不可复选(start)
// **start是开始建立的第一条分支,从第二条分支开始减
func subsetsWithDup(nums []int) [][]int {
res := [][]int {}
set := []int {}
sort.Ints(nums)
var backtrack func(start int)
backtrack = func( start int) {
tmp := make([]int,len(set))
// set后续还得使用
copy(tmp, set)
res = append(res,tmp)
// 自带了终止条件
for i := start; i < len(nums); i ++ {
// **start是开始建立的第一条分支,从第二条分支开始减
if i > start && nums[i] == nums[i -1] {
continue
}
set = append(set, nums[i])
// 递归进入到下一层,只选比自己大的了
backtrack(i + 1)
set = set[:len(set)-1]
}
}
backtrack(0)
return res
}
组合
相信自己
for- start处理不重复
func combine(n int, k int) [][]int {
path := []int {}
res := [][]int{}
dfs := func (start int) {}
dfs = func (start int) {
if len(path) == k {
temp := make([]int, len(path))
copy(temp, path)
res = append(res, temp)
return
}
for i := start; i <= n; i ++ {
// [1]
path = append(path, i)
// [2]
dfs(i+1)
path = path[:len(path) - 1]
}
}
dfs(1)
return res
}
39. 组合总和
新的去重逻辑,还是start
func combinationSum(candidates []int, target int) [][]int {
path := []int {}
pathSum := 0
res := [][]int {}
var dfs func(start int)
dfs = func (start int) {
if pathSum == target {
tmp := make([]int, len(path))
copy(tmp, path)
res = append(res, tmp)
}
if pathSum > target {
return
}
for i := start; i < len(candidates); i ++ {
path = append(path, candidates[i])
pathSum += candidates[i]
dfs(i)
last := path[len(path)-1]
path = path[:len(path)-1]
pathSum -= last
}
}
dfs(0)
return res
}
** 省时间
** 相邻相同不再展开
** 组合式
func combinationSum2(candidates []int, target int) [][]int {
var path []int
var sumPath int
var res [][]int
var dfs func(start int)
sort.Ints(candidates)
dfs = func (start int) {
if sumPath == target {
tmp := make([]int, len(path))
copy(tmp, path)
res = append(res, tmp)
return
}
// ** 省时间
if sumPath > target {
return
}
for i := start; i < len(candidates); i++{
// ** 相邻相同不再展开
if i > start && candidates[i] == candidates[i-1] {
continue
}
path = append(path, candidates[i])
sumPath += candidates[i]
// ** 组合式
dfs(i+1)
last := path[len(path)-1]
path = path[:len(path)-1]
sumPath -= last
}
}
dfs(0)
return res
}