如果你能够成功的生成所有无重子集,那么你稍微改改代码就能生成所有无重组合了。

你比如说,让你在 nums = [1,2,3] 中拿 2 个元素形成所有的组合,你怎么做?

稍微想想就会发现,大小为 2 的所有组合,不就是所有大小为 2 的子集嘛。

所以我说组合和子集是一样的:大小为 k 的组合就是大小为 k 的子集

比如力扣第 77 题「组合」:

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

函数签名如下:

List<List<Integer>> combine(int n, int k)

比如 combine(3, 2) 的返回值应该是:

[ [1,2],[1,3],[2,3] ]

这是标准的组合问题,但我给你翻译一下就变成子集问题了:

给你输入一个数组 nums = [1,2..,n] 和一个正整数 k,请你生成所有大小为 k 的子集

还是以 nums = [1,2,3] 为例,刚才让你求所有子集,就是把所有节点的值都收集起来;现在你只需要把第 2 层(根节点视为第 0 层)的节点收集起来,就是大小为 2 的所有组合

image.png

反映到代码上,只需要稍改 base case,控制算法仅仅收集第 k 层节点的值即可:

class Solution {
 
    List<List<Integer>> res = new LinkedList<>();
    // 记录回溯算法的递归路径
    LinkedList<Integer> track = new LinkedList<>();
 
    // 主函数
    public List<List<Integer>> combine(int n, int k) {
        backtrack(1, n, k);
        return res;
    }
 
    void backtrack(int start, int n, int k) {
        // base case
        if (k == track.size()) {
            // 遍历到了第 k 层,收集当前节点的值
            res.add(new LinkedList<>(track));
            return;
        }
        
        // 回溯算法标准框架
        for (int i = start; i <= n; i++) {
            // 选择
            track.addLast(i);
            // 通过 start 参数控制树枝的遍历,避免产生重复的子集
            backtrack(i + 1, n, k);
            // 撤销选择
            track.removeLast();
        }
    }
}
 

回溯算法秒杀所有排列-组合-子集问题 | labuladong 的算法笔记