参考

回溯算法简介

DFS 和回溯算法区别

DFS 是一个劲的往某一个方向搜索,而回溯算法建立在 DFS 基础之上的,但不同的是在搜索过程中,达到结束条件后,恢复状态,回溯上一层,再次搜索。因此回溯算法与 DFS 的区别就是有无状态重置

何时使用回溯算法

当问题需要 “回头”,以此来查找出所有的解的时候,使用回溯算法。即满足结束条件或者发现不是正确路径的时候(走不通),要撤销选择,回退到上一个状态,继续尝试,直到找出所有解为止

怎么样写回溯算法

(从上而下,※代表难点,根据题目而变化)
①画出递归树,找到状态变量(回溯函数的参数),这一步非常重要※
②根据题意,确立结束条件
③找准选择列表(与函数参数相关),与第一步紧密关联※
④判断是否需要剪枝
⑤作出选择,递归调用,进入下一层
⑥撤销选择

回溯问题的类型

这里先给出,我总结的回溯问题类型,并给出相应的 leetcode题目(一直更新),然后再说如何去编写。特别关注搜索类型的,搜索类的搞懂,你就真的搞懂回溯算法了,,是前面两类是基础,帮助你培养思维

注意:子集、组合与排列是不同性质的概念。子集、组合是无关顺序的,而排列是和元素顺序有关的,如 [1,2] 和 [2,1] 是同一个组合(子集),但 [1,2] 和 [2,1] 是两种不一样的排列!!!!因此被分为两类问题

例题

子集、组合类型问题

子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

解题步骤如下

1、画递归树

递归树

观察上图可得,选择列表里的数,都是选择路径(红色框)后面的数,比如[1]这条路径,他后面的选择列表只有 “2、3”,[2] 这条路径后面只有 “3” 这个选择,那么这个时候,就应该 使用一个参数 start,来标识当前的选择列表的起始位置。也就是标识每一层的状态,因此被形象的称为 “状态变量“,最终函数声明如下

public void backtrace(int[] nums,List<Integer> path, int start);
// path用于记录结果集,要把最后的path加入结果集
// start 用于记录每一层的状态
2、找结束条件

此题非常特殊,所有路径都应该加入结果集,所以不存在结束条件。或者说当 start 参数越过数组边界的时候,程序就自己跳过下一层递归了,因此不需要手写结束条件,直接加入结果集。

3、找选择列表

在1中已经提到过了,子集问题的选择列表,是上一条选择路径之后的数,即

for(int i=start;i<nums.size();i++)
4、做出选择(即for 循环里面的)
public void backtrace(int[] nums,List<Integer> path, int start,List res)
{
    for(int i=start;i<nums.length;i++)
    {
        // 加入结果集
        path.add(nums[i]);
        // 对于 java 来说 需要把路径 “复制一遍”再添加
        res.add(new ArrayList<>(path));
        backtrace(nums,path,i+1,res);

    }
}
5、撤销选择

整体的 backtrack 函数如下

public void backtrace(int[] nums,List<Integer> path, int start,List res)
{
    for(int i=start;i<nums.length;i++)
    {
        // 加入结果集
        path.add(nums[i]);
        res.add(new ArrayList<>(path));
        backtrace(nums,path,i+1,res);
        
        // 撤销选择
        path.remove(path.size()-1);
    }
}