文章:代码随想录
回溯算法其实就是暴力for循环,但是单纯for循环无法做到一些需要回溯的场景,今天做的就是这一道入门题 组合。
思路:
这一道题没办法直接通过for暴力求解,因为k是不固定的,k等于2可以用两层嵌套for去做,那嵌套k=50呢?手写50层for循环吗。所以这一题的暴力得用for和递归相结合,这就是经典的回溯算法.
那么我们的开始位置其实就是i,然后往i+1的位置往后递归,当收集到k个时,加入结果集,递归函数结束。回弹到上一层时,需要把最后加入的数字弹出,然后进入这一层的下一个循环i++.这其实和二叉树有一道打印出所有路径的题目很像,dfs前序遍历,然后从叶子节点往上逐层弹出上一个加入的节点。其实所有的回溯算法和这个原理是一样的。这里收集到k个其实就是遍历到了叶子节点,所以往上弹出最后一个到K-1的状态。然后再往右节点遍历找下一个最后元素。
代码:
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
backTracking(n, k, 1);
return result;
}
private void backTracking(int n, int k, int start) {
if (path.size() == k) {
result.add(new ArrayList<Integer>(path));
return;
}
for (int i = start; i <= n; i++) {
path.add(i);
backTracking(n, k, i + 1);
//回溯
path.remove(path.size() - 1);
}
}
//剪枝优化
//在剩下的元素数量数不能满足组成k的时候,这种分支其实可以修剪掉。
//1.已经选择的元素个数:path.size();
//2.还需要的元素个数为: k - path.size();
//那么n-(k - path.size())就是最后一个能接受的开始位置,因为在这个位置之后开始,后面的元素已经不够组成k个了.
//我们是从1开始,所以最后还要加1.-->n-(k - path.size())+1
private void backTracking2(int n, int k, int start) {
if (path.size() == k) {
result.add(new ArrayList<Integer>(path));
return;
}
for (int i = start; i <= n - (k - path.size()) + 1; i++) {
path.add(i);
backTracking(n, k, i + 1);
//回溯
path.remove(path.size() - 1);
}
}