题目

给定两个整数 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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combinations
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

经典的回溯问题,不同于排列问题,组合不考虑顺序,例如77. 组合 - 图177. 组合 - 图2是同一种组合方式,因此需要避免这两者重复地加入结果集。

第一种思路是,和我们把它当做数学题的思路一样,比如77. 组合 - 图377. 组合 - 图477. 组合 - 图5中选两个,那么第一个数有四种选择,可选77. 组合 - 图6,第二个数的选择就和第一个数有关系,只能选大于第一个数的,因为前面提到的,组合不讲究顺序,需要避免重复添加。这种方法的递归树如下:
image.png

第二种思路是可以依次考虑每个数选与不选,选够了77. 组合 - 图8个就表示获得了一种组合方式。这种方法的递归树如下图,从77. 组合 - 图9开始考虑,每一个分支表示当前数选与不选,左分支为选,右分支不选,划横线的就是最终的77. 组合 - 图10种组合方式,可以看出不同于第一种方法答案都在一层,这里不在一层。鼠标画画实在太难了,丑的自己也看不下去…
image.png

代码

方法一 考虑当前一步选哪个数

  1. class Solution {
  2. List<List<Integer>> ans;
  3. public List<List<Integer>> combine(int n, int k) {
  4. ans = new ArrayList<>();
  5. Deque<Integer> path = new ArrayDeque<>();
  6. dfs(1, k, n, path);
  7. return ans;
  8. }
  9. private void dfs(int begin, int k, int n, Deque<Integer> path) {
  10. if (path.size() == k) {
  11. ans.add(new ArrayList<>(path));
  12. return;
  13. }
  14. // 当前位置考虑的起始范围由上一个数决定,只能往后考虑
  15. for (int i = begin; i <= n; i++) {
  16. path.push(i);
  17. dfs(i + 1, k, n, path);
  18. path.poll();
  19. }
  20. }
  21. }

方法二 从1到n考虑每个数选与不选

  1. class Solution {
  2. List<List<Integer>> ans;
  3. public List<List<Integer>> combine(int n, int k) {
  4. ans = new ArrayList<>();
  5. Deque<Integer> path = new ArrayDeque<>();
  6. dfs(1, k, n, path);
  7. return ans;
  8. }
  9. private void dfs(int index, int k, int n, Deque<Integer> path) {
  10. if (path.size() == k) {
  11. ans.add(new ArrayList<>(path));
  12. return;
  13. }
  14. if (index > n) {
  15. return;
  16. }
  17. // 选择当前数index
  18. path.push(index);
  19. dfs(index + 1, k, n, path);
  20. path.poll();
  21. // 不选当前数index
  22. dfs(index + 1, k, n, path);
  23. }
  24. }