​77. 组合​

77. 组合

难度中等953收藏分享切换为英文接收动态反馈

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

示例 1:

输入:n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

示例 2:

输入:n = 1, k = 1
输出:[[1]]

提示:

  • 1 <= n <= 20
  • 1 <= k <= n

代码思路

对于递归函数dfs,步骤如下:

  1. 如果当前组合已满k个数,将这个组合加入最终结果,退出。
  2. 如果已经访问过所有的数,退出。
  3. 可行性剪枝处理:如果接下来的数都放入当前的状态数组,也不足k个,退出。
  4. 假设当前的数pos加入数组,进行下一步递归,结束后,回溯,弹出数组末端的数。
  5. 假设当前的数pos不加入数组,直接进行下一步递归。
  6. 递归结束。

复杂度分析

设待选的最大的数为N,要选的数为K

C(N, K)代表N个数中挑选出K个不同数的组合数个数。

时间复杂度

  • N层的满二叉树的状态共2^N个,未剪枝的情况下时间复杂度为O(2^N)
  • 剪枝后可以减少大量的搜索节点,时间复杂度可达到O(C(N, K))

空间复杂度

  • 递归的空间复杂度取决于搜索树的最大深度,最大深度为N,空间复杂度为O(N * K)
class Solution {
    public List<List<Integer>> combine(int n, int k) {
        List<Integer> combination = new ArrayList<Integer>();
        List<List<Integer>> combinations = new ArrayList<List<Integer>>();
        dfs(1,combination,n,k,combinations);
        return combinations;
    }
    // pos代表已访问到哪个数,combination代表当前地组合
    public void dfs(int pos,List<Integer> combination,int n,int k,List<List<Integer>> combinations) {
        // 第一个递归出口,如果满足 k 个,将这种组合加入最终结果中
        if(combination.size() == k) {
            combinations.add(new ArrayList(combination));
            return;
        }
         // 第二个递归出口,如果以访问完1 ~ n所有数字,退出当前函数
        if(pos > n ) {
            return;
        }
        // 可行性剪枝,如果后面的数都放入,个数不足k的话,退出
        if(combination.size() + n - pos + 1 < k) {
            return;
        }
        combination.add(pos);
        dfs(pos+1,combination,n,k,combinations);
        combination.remove(combination.size() - 1);//id
        dfs(pos+1,combination,n,k,combinations);
    }

}

题意概述:给定一个长度为 $N$ 的排列 $A$,对于所有的 $1\leq L\leq R\leq N$,将 $A_L,A_{L+1},\cdots,A_R$ 进行翻转得到一个新的排列 $f(L,R)$,将所有这样的排列按字典序排序,求第 $K$ 个排列。其中字典序定义为:对于两个序列 $S$ 和 $T$,如果 $S$ 是 $T$ 的前缀,且 $S$ 和 $T$ 在第一个不同的位置上的数的大小关系,那么 $S$ 和 $T$ 的字典序关系就确定了。 这个题目需要使用一些排列组合的知识和思想。我们先来考虑一下如何计算出 $f(L,R)$。对于一个排列 $A$,我们可以将其分成三个部分: $$A=a_1a_2\cdots a_{L-1}a_La_{L+1}\cdots a_{R-1}a_Ra_{R+1}\cdots a_N$$ 我们将 $a_L,a_{L+1},\cdots,a_R$ 进行翻转,得到的排列为: $$f(L,R)=a_1a_2\cdots a_{L-1}a_Ra_{R-1}\cdots a_{L+1}a_La_{R+1}\cdots a_N$$ 我们发现,如果我们枚举 $R$,那么 $L$ 的范围就是 $[1,R]$,也就是说,我们可以枚举 $R$,然后对于每个 $R$,枚举 $L$,计算出 $f(L,R)$,这样就能得到所有的排列 $f(L,R)$。 接下来,我们考虑如何计算第 $K$ 个排列。我们可以先计算出所有排列 $f(L,R)$,然后将其排序,最后取第 $K$ 个即可。但是这个做法的时间复杂度是 $O(N^3\log N)$,不太能通过本题。 我们可以换一种思路,考虑如何对于一个排列 $f(L,R)$,计算出它是第几个排列。假设 $f(L,R)$ 在所有排列中的位置为 $pos$,我们可以通过计算 $pos$ 来确定第 $K$ 个排列。 我们将 $f(L,R)$ 和 $A$ 比较,可以发现,$f(L,R)$ 和 $A$ 在前 $L-1$ 个位置上是相同的,而 $f(L,R)$ 和 $A$ 在 $L$ 到 $R$ 位置的相对位置关系是相反的。也就是说,如果 $f(L,R)$ 中的第 $i$ 个数是 $a_p$,那么 $A$ 中的第 $i$ 个数就是 $a_{L+R-p-i}$。 我们可以发现,对于所有的 $R$,$f(L,R)$ 和 $f(L,R+1)$ 只有第 $L$ 到 $R$ 个数的相对位置关系不同,其他位置都是相同的。也就是说,如果我们知道了 $f(L,R)$ 在所有排列中的位置 $pos_{L,R}$,那么对于 $f(L,R+1)$,它在所有排列中的位置就是 $pos_{L,R}+\Delta$,其中 $\Delta$ 表示 $f(L,R)$ 和 $f(L,R+1)$ 的第 $L$ 到 $R$ 个数的相对位置关系不同的个数。我们可以将 $\Delta$ 看做一个序列的逆序对数,然后通过归并排序的思想,计算出 $\Delta$ 的值。 对于计算 $pos_{L,R}$,我们可以使用类似康托展开的方法,计算出前面有多少个排列是以 $f(L,R)$ 开头的。具体地,我们可以计算出 $f(L,R)$ 中有多少个数比 $a_L$ 小,然后乘上 $(N-R+1)!$,再加上 $f(L,R)$ 除去第一个数以外的部分的排列的个数,即 $pos_{L+1,R}$。这样就可以递归计算出 $pos_{L,R}$ 了。 最终的时间复杂度为 $O(N^2\log N)$,可以通过本题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值