77. 组合
    
   
    难度中等471给定两个整数
    
     n
    
    和
    
     k
    
    ,返回 1 …
    
     n
    
    中所有可能的
    
     k
    
    个数的组合。
   
    
     示例:
    
   
输入: n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]
回溯法: 是在一棵树上深度优先遍历(欣慰找到所有的解,所以要遍历)
根据搜索起点画出二叉树
    深度优先遍历,因此首先画出树形结构,
    
     n = 4, k = 2
    
    ,我们可以发现如下递归结构:
   
- 如果组合里有 1 ,那么需要在 [2, 3, 4] 里再找 11 个数;
- 
     如果组合里有 2 ,那么需要在 [3, 4] 里再找 11数。注意:这里不能再考虑 11,因为包含 11 的组合,在第 1 种情况中已经包含。
 
   
说明:
- 叶子节点的信息体现在从根节点到叶子结点的路径上,因此需要一个表示路径的变量path…他是一个列表,特别的,path是一个栈
- 
     每一个结点递归地作同样的事情,区别在于搜索起点,因此需要一个变量
 
 start
 
 ,表示在区间
 
 [begin, n]
 
 里选出若干个数的组合;
 class Solution {
    public List<List<Integer>> combine(int n, int k) {
        List<List<Integer>> res = new ArrayList<>();
        if (k <= 0 || n < k) {
            return res;
        }
        // 一个表示路径的变量 path
        Deque<Integer> path = new ArrayDeque<>();
        dfs(n, k, 1, path, res);
        return res;
    }
    private void dfs(int n, int k, int begin, Deque<Integer> path, List<List<Integer>> res) {
        // 递归终止条件是:path 的长度等于 k
        if (path.size() == k) {
            res.add(new ArrayList<>(path));
            return;
        }
        // 遍历可能的搜索起点
        for (int i = begin; i <= n; i++) {
            // 向路径变量里添加一个数
            path.addLast(i);
            // 下一轮搜索,设置的搜索起点要加 1,因为组合数理不允许出现重复的元素
            dfs(n, k, i + 1, path, res);
            // 重点理解这里:深度优先遍历有回头的过程,因此递归之前做了什么,递归之后需要做相同操作的逆向操作
            path.removeLast();
        }
    }
}
    
    
    优化:分析搜索起点的上界进行剪枝
   
// 从当前搜索起点 begin 遍历到 n
for (int i = begin; i <= n; i++) {
    path.addLast(i);
    dfs(n, k, i + 1, path, res);
    path.removeLast();
}
    事实上如果
    
     n = 7, k = 4
    
    ,从5开始搜索就没有什么意义,这是因为:即使把 55 选上,后面的数只有 66 和 77,一共就 33 个候选数,凑不出 44 个数的组合。因此,
    
     搜索起点有上界
    
    ,这个上界是多少,可以举几个
   
搜索起点的上界 = n - (k - path.size()) + 1
    所以,我们的剪枝过程就是:把
    
     i <= n
    
    改成
    
     i <= n - (k - path.size()) + 1
    
    :
   
class Solution {
    public List<List<Integer>> combine(int n, int k) {
        List<List<Integer>> res = new ArrayList<>();
        if (k <= 0 || n < k) {
            return res;
        }
        //一个表示路径的变量 path
        Deque<Integer> path = new ArrayDeque<>();
        dfs(n, k, 1, path, res);
        return res;
    }
    private void dfs(int n, int k, int index, Deque<Integer> path, List<List<Integer>> res) {
        //退出递归
        if (path.size() == k) {
            res.add(new ArrayList<>(path));
            return;
        }
        // 只有这里 i <= n - (k - path.size()) + 1 与参考代码 1 不同
        for (int i = index; i <= n - (k - path.size()) + 1; i++) {
            //  // 向路径变量里添加一个数
            path.addLast(i);
            
            // 下一轮搜索,设置的搜索起点要加 1,因为组合数理不允许出现重复的元素
            dfs(n, k, i + 1, path, res);
            //回溯
            // 深度优先遍历有回头的过程,因此递归之前做了什么,递归之后需要做相同操作的逆向操作
            path.removeLast();
        }
    }
}
 
版权声明:本文为qq_44236958原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
