
 回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,如果想让回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是穷举的本质。
那么既然回溯法并不高效为什么还要用它呢?
因为没得选,一些问题能暴力搜出来就不错了,撑死了再剪枝一下,还没有更高效的解法。
回溯法解决的问题都可以抽象为树形结构,是的,我指的是所有回溯法的问题都可以抽象为树形结构!
因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度构成的树的深度。
递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)。
回溯模板:
void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }
    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}
77. 组合
题目链接
题目描述:
 给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
示例:
 输入: n = 4, k = 2
 输出:
 [
 [2,4],
 [3,4],
 [2,3],
 [1,2],
 [1,3],
 [1,4],
 ]
难点:
 剪枝
 
思路:
时间复杂度:O()
 空间复杂度:O()
class Solution {
    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;
    }
    public void backtracking(int n, int k, int startIdx) {
        if (n < 1 || n < k) return;
        if (path.size() == k) {
            result.add(new ArrayList<>(path));
            // result.add(path); 这是错误的!
            return;
        }
        for (int i = startIdx; i <= n; i++) {
            path.add(i);
            backtracking(n, k, i+1);
            path.remove(path.size()-1);
        }
    }
}
剪枝:
for (int i = startIdx; i <= n-(k-path.size())+1; i++) {
    path.add(i);
    backtracking(n, k, i+1);
    path.remove(path.size()-1);
}
时长:
 15min
收获:
 注意了!向列表中添加对象元素时,要考虑这个对象被添加后是否不能改变了。如果不能改变,必须要重新new一个对象,而不是指向原来的对象,否则结果集中的结果会产生改变。



















