内容:
- 组合(77)
1. 组合
难度:🔥🔥🔥
建议:对着在回溯算法理论基础给出的代码模板,来做本题组合问题,大家就会发现 写回溯算法讨论。
在回溯算法解决实际问题的过程中,大家会有各种疑问,先看视频介绍,基本可以解决大家的疑惑。
本题关于剪枝操作是大家要理解的重点,因为后面很多回溯算法解决的题目,都是这个剪枝套路。
1.1 思路分析
这道题目是回溯算法组合问题的经典题目。
我们一般将组合问题抽象成为如下树形结构,如图:
而树的宽度与树的深度分别对应题目中的n
,k
。图中每次搜索到了叶子节点,我们就找到了一个结果。
我们接下来使用回溯三部曲即可解决这道题目。
1.2 代码实现
class Solution {
//使用LinkedList
LinkedList<Integer> path = new LinkedList<>();
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
backtracking(n,k,1);
return result;
}
/**
*
* @param n [1,n] 之间的数
* @param k 一共k个数
* @param startIndex 开始的索引数
*/
private void backtracking(int n,int k,int startIndex){
if(path.size() == k){
//这里不可以直接使用path
result.add(new ArrayList<>(path));
return;
}
//剪枝操作 n -> n - (k - path.size()) + 1
for(int i = startIndex;i <= n - (k - path.size()) + 1;i++){
path.add(i);
backtracking(n,k,i + 1);
path.removeLast();
}
}
}
1.3 注意事项
- 在问题中我们可以对回溯算法进行一些剪枝操作
如图:
所以在for(int i = startIndex;i <= n;i++)
中我们可以优化为
for(int i = startIndex;i <= n - (k - path.size()) + 1;i++)
优化过程如下:
- 已经选择的元素个数:
path.size()
; - 所需要的元素个数为:
k - path.size()
; - 列表中剩余元素
(n-i) >=
所需要的元素个数(k - path.size())
- 在集合n中至多要从该起始位置 :
i <= n - (k - path.size()) + 1
,开始遍历
为什么有个+1呢,因为包括起始位置,我们要是一个左闭的集合。
举个例子,n = 4,k = 3, 目前已经选取的元素为0(path.size
为0),n - (k - 0) + 1
即 4 - ( 3 - 0) + 1 = 2
,所以最大从2开始搜索,从3开始则不行。
- 向
result
集合中添加中间路径path
时不能直接加入path
,因为path
实际上是地址,而无论加入多少个path
都是同一个对象。并且在回溯后都会清空,最后得到的集合都为空。所以需要我们去new
一个ArrayList()
1.4 收获总结
回溯算法模板–来自代码随想录
//for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}