回溯算法理论基础
77. Combinations
题目链接:77. Combinations
思路链接:代码随想录回溯算法-组合问题
思路
将这题设计到的回溯算法抽象为树形结构。通过遍历数组中的每一个元素,遍历到每一个元素时,在该元素后面的子数组中取一个数与当前遍历到的元素组成一个集合,直到无法组合为止。
利用回溯操作,每得到一个组合时,就回退到还没在子数组中取数时的状态。
因为回溯算法本质是暴力遍历每一个节点,这里可以发现有一部分遍历是无意义的,因此可以通过剪枝操作来提升运行速度。
Code
import java.util.List;
import java.util.LinkedList;
class Solution {
// 回溯法
// 定义两个需要处理结果的变量
private List<List<Integer>> result = new LinkedList<>();
private List<Integer> path = new LinkedList<>();
// 定义回溯函数
// 需要处理的参数:n, k, startIndex startIndex是为了能够避免出现重复的组合,用来记录本层递归的中,集合从哪里开始遍历
// 需要返回的变量: void 因为直接处理result和paths
private void backtracking(int n, int k, int startIndex) {
// 结束条件:如果paths的size == k,将找到的path加入result,结束递归
if (path.size() == k) {
result.add(new LinkedList<>(path));
return;
}
// 遍历所有元素
// for (int i = startIndex; i <= n; i++) { // 这里可以通过剪枝优化,减少遍历的节点数
for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) { // 这里可以通过剪枝优化,减少遍历的节点数,只要遍历到i至多能遍历到的元素就行,在这之后的都是无意义的遍历
// path.size(): 已经选取的元素
// k - path.size(): 还有多少元素还需要加入
// n - (k - path.size()) + 1:i至多在数组中遍历到的元素
// 举个例子,n = 4,k = 3, 目前已经选取的元素为0(path.size为0),n - (k - 0) + 1 即 4 - ( 3 - 0) + 1 = 2。
从2开始搜索都是合理的,可以是组合[2, 3, 4]。
// System.out.println("i: " + i + "; startIndex: " + startIndex);
// 处理节点,把每次遍历到的元素加入path(第一个值)
path.add(i);
// 递归操作,也就是加入下一个值(第二个值),为了防止重复,此时的startIndex就是i下一个数
backtracking(n, k, i + 1);
// 回溯操作,回退到path只加入第一个值的状态
path.remove(path.size() - 1);
}
}
public List<List<Integer>> combine(int n, int k) {
backtracking(n, k, 1);
return result;
}
}