回溯算法理论基础
文字讲解:回溯算法理论基础
定义:回溯法穿插在递推的过程中,本质上是一种暴力搜索算法,穷举所有可能。其解决的以下问题都可以抽象为树形结构。
- 组合问题:N个数里面按一定规则找出k个数的集合
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等等
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
第77题. 组合
文字讲解:组合
题设:给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。
思路:所有回溯算法的题目,都要先将问题抽象为树形结构,本题如下图:
<img src="https://code-thinking-1253855093.file.myqcloud.com/pics/20201123195223940.png" alt="77.组合" style="zoom:50%;" />
回溯法三部曲:
-
递归函数的返回值以及参数:
在这里要定义两个全局变量,一个用来存放符合条件单一结果,一个用来存放符合条件结果的集合。
<img src="https://code-thinking-1253855093.file.myqcloud.com/pics/20201123195328976.png" alt="77.组合2" style="zoom:50%;" />
-
回溯函数终止条件:
path数组的大小达到k,说明找到了一个子集大小为k的组合,此时用result二维数组,把path保存起来,并终止本层递归。
<img src="https://code-thinking-1253855093.file.myqcloud.com/pics/20201123195407907.png" alt="77.组合3" style="zoom:50%;" />
-
单层搜索的过程:
for循环用来横向遍历,递归的过程是纵向遍历。
<img src="https://code-thinking-1253855093.file.myqcloud.com/pics/20201123195242899.png" alt="77.组合1" style="zoom:50%;" />
未剪枝操作如下:
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> combine(int n, int k) {
backtracking(n, k, 1);
return result;
}
public void backtracking(int n, int k, int startIndex) {
if (path.size() == k) {
result.add(new ArrayList<>(path));
return;
}
for (int i = startIndex; i <= n; i++) {
path.add(i);
backtracking(n, k, i + 1);
path.removeLast();
}
}
}
下图中每一个节点(图中为矩形),就代表本层的一个for循环,那么每一层的for循环从第二个数开始遍历的话,都没有意义,都是无效遍历。可以剪枝的地方就在递归中每一层的for循环所选择的起始位置。如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了。
<img src="https://code-thinking-1253855093.file.myqcloud.com/pics/20210130194335207-20230310134409532.png" alt="77.组合4" style="zoom:50%;" />
剪枝操作如下:
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> combine(int n, int k) {
backtracking(n, k, 1);
return result;
}
public void backtracking(int n, int k, int startIndex) {
if (path.size() == k) {
result.add(new ArrayList<>(path));
return;
}
for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) {
path.add(i);
backtracking(n, k, i + 1);
path.removeLast();
}
}
}
优化过程如下:
- 已经选择的元素个数:
path.size()
; - 还需要的元素个数为:
k - path.size()
; - 在集合n中至多要从该起始位置 :
n - (k - path.size()) + 1
,开始遍历。