回溯作为常见的算法之一,相信大家也曾经遇到过回溯相关的问题,觉得摸不着头脑,实际上回溯相关的问题也是有套路的。
一、什么是回溯
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就 “回溯” 返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为 “回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。
(以上解释摘自Leetcode回溯专题介绍,另外建议大家可以按照专题来刷题)
所以可以看出来,回溯就是一种深度优先遍历,先选择一条路走,直到走不通就退回来选择另一条路。
二、回溯的框架
有了上面的初步解释后,就可以得到回溯的基本框架,我们需要一些变量:所有合理的路径就是结果集,正在进行的某一个路径
定义结果集
定义当前路径
back(路径,选择列表){
if(结束条件){
结果集中加入当前路径
}
for(选择 in 选择列表){
在可选择路径中选择一条路
back(当前路径,可继续选择的分支节点)
撤销选择
}
}
所以我们在考虑结果集时,就得考虑什么时候结束、选择列表分别表示什么
三、全排列
[46] 全排列
首先题目声明:没有重复,那么我们就不需要使用一个数据保存已经使用过的节点状态。只要当前路径中没有存在过该数,就加入。
由于是全排列,每个数字都能作为开头,所以我们在循环时就要从nums[0]开始循环。如果遇到使用过的节点就跳过。代码如下:
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> tmp = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
if (nums.length == 0) {
return res;
}
back(nums);
return res;
}
private void back(int[] nums) {
if (tmp.size() == nums.length) {
res.add(new ArrayList(tmp));
return ;
}
for (int i = 0; i < nums.length; i ++) {
//每次都从0开始
if (tmp.contains(nums[i])) continue; //遇到使用过的就跳过
tmp.add(nums[i]); //选择
back(</