回溯算法学习心得
一、回溯算法
回溯算法的原理:回溯算法是一种选优搜索法,按照选优条件向前搜索,以达到目标。但当探索到某一步的时候,发现原先选择并不优活着达不到目标的时候,就退回一步重新选择,这种走不通就退回再走的技术为回溯法。
废话不多说,直接上解决回溯算法的框架。解决一个回溯问题,实际上就是一个决策树的遍历过程。你只需要思考如下3个问题:
- 路径:也就是已经做出的选择
- 选择列表:也就是你当前可以做的选择
- 结束条件:也就是到达决策树的底层,无法再做选择的条件
回溯法的算法思想和深度优先搜索(DFS)的思想较为类似,可以结合着看
代码方面,回溯算法的框架如下所示:
result = []
def backtrack(路径,选择列表):
if 满足结束条件 :
result.add(路径)
return
for 选择 in 选择列表 :
做选择
backtrack(路径,选择列表)
撤销选择
其核心就是for循环里面的递归,在递归调用之前“做选择”,在递归调用之后“撤销选择”,就是这样。
案例一:全排列问题
问题描述如下图所示:
![image-20220326170514266](/Users/weiyifeng/Library/Application Support/typora-user-images/image-20220326170514266.png)
解决问题的代码如下所示,可结合起来进行理解:
class Solution {
List<List<Integer>> result = new LinkedList<>();
public List<List<Integer>> permute(int[] nums) {
LinkedList<Integer> track = new LinkedList<>();
backtarck(nums,track);
return result;
}
void backtarck(int[] nums , LinkedList<Integer> track){
//当到达叶子结点的时候,就将当前的track装入结果列表
if(track.size() == nums.length ) {
result.add(new LinkedList(track));
return;
}
for(int i = 0 ; i < nums.length ; i++) {
//排除当前数组中不合法的选择
if(track.contains(nums[i])) {
continue;
}
//做选择
track.add(nums[i]);
//进入下一层决策树
backtarck(nums,track);
//取消选择
track.removeLast();
}
}
}
二、回溯算法在其他场景下的应用
2.1 求子集
问题描述如下所示:
![image-20220326171721726](/Users/weiyifeng/Library/Application Support/typora-user-images/image-20220326171721726.png)
拓展内容:
计算递归算法时间复杂度的方法:找到递归深度,然后乘以每次递归中迭代的次数
解法思想:
首先,空集[]
肯定是一个子集。
然后,以1开头的子集有哪些呢? 有[1] [1,2] [1,2,3]
同理,以2开头的子集有[2] [2,3]
以3开头的子集有[3]
最后,把上面这些子集加起来就是【1,2,3】的所有子集
代码解法如下所示:
class Solution {
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
LinkedList<Integer> track = new LinkedList<>();
backTrack(nums,0,track);
return result;
}
void backTrack(int[] nums , int start , LinkedList<Integer> track) {
//前序遍历的位置
result.add(new LinkedList(track));
for(int i = start ; i < nums.length ; i++) {
//做选择
track.add(nums[i]);
//递归回溯
backTrack(nums , i + 1 , track);
//撤销选择
track.removeLast();
}
}
}
可以看见,由start
参数实现了刚才所说的“以某个数字开头的子集”;而对res
的更新处在前序遍历的位置,所以说res
记录了树上的所有结点,也就是所有的子集
2.2 回溯算法的最佳实践:括号生成
有关括号问题,你只需要记住以下性质,思路就很容易想出来:
- 一个“合法”括号组合的左括号数量一定等于有括号数量
- 对于一个“合法”的括号字符串组合
p
,必然对于任何0 <= i < len(p)
都有:子串p[0...i]
中左括号的数量都大于或者等于右括号的数量