回溯算法

100 阅读2分钟

回溯需要考虑的三个问题

  • 路径

  • 选择列表: 每一层for循环是下一层要去展开的列表

  • 结束条件

  • 重复/复选

框架

visited = map[int]boool{}
result = []int {}
def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return
    
    for 选择 in 选择列表:
        剪枝
        做选择
        标记visited[]
        backtrack(路径, 选择列表)
        撤销选择
        取消标记vistied[]

46. 全排列

无重()不可复选(visited)

最经典的回溯,用长度来控制递归深度。穷举所有可能,用全局visited[]来记录当前的这条分支上节点的使用情况,避免重复使用,visited[]充当了剪枝的效果

image.png

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. 子集

image.png 元素无重不可复选

  1. copy
  2. 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)

image.png

// **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
}

组合

image.png 相信自己 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

image.png

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
}

40. 组合总和 II

** 省时间

** 相邻相同不再展开

** 组合式

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
}