LC77-组合

  • Post author:
  • Post category:其他





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 版权协议,转载请附上原文出处链接和本声明。