题目

  • 找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
  • 说明:
    • 所有数字都是正整数。
    • 解集不能包含重复的组合。
  • 示例1:

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

  • 示例2:

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

解题思路

  1. 在方法的开始,首先先确定所给定的k和n是否能够有解

    • 给定k个数,那么这k个数的最小值为1 + 2 + … + k = (1 + k) * k / 2 //首项为1,公差为1的等差数列;
    • k个数的最大值为(n - k + 1) + (n - k + 2) + … + n = (19 - k) * k / 2 //末项为9,公差为1的等差数列;
    • 由此可得,只有当n >= (1 + k) * k / 2 并且 n <= (19 - k) * k / 2时才会有解,如果k和n不满足条件,则直接返回空列表。
  2. 核心思想

    • 现在需要用k个数之和表示n:
      • 当k = 1时,target = n,list中直接放入n;
      • 当k = 2时,假定先在list中放入一个数i(1<=i<=9),那么还需要放入n - i,相当于调用k = 1情况,此时target = n - i;
      • 当k = 3时,假定先在list中放入一个数i(1<=i<=9),那么还需要放入n - i,相当于调用k = 2情况,此时target = n - i;
      • ·······
      • 由此,我们可以发现一个规律:当我们在计算某一个k值的情况时,可以先放入一个元素i,然后递归调用target = n = i的k - 1情况,以此类推,递归终止条件即为k = 1的情况,此时如果target <= 9,那么放入target后即为我们想要的解。
      • 总公式:第k个list[target = n] = [i].append(第k - 1个list[target = n - i])
  3. 开始计算

    • 方法中维护了四个变量:rest(剩余数字个数),target(当前和还缺多少),sum(题目需要求的总和,也可使用全局变量记录,currentSum(当前列表中已有元素的和,也可使用sum - target计算)
    • 首先,当剩余个数为1,但target仍大于9(rest == 1 && target > 9),即还剩一个元素的位置,但却需要一个大于9的数来满足题意,无法达到目标,因此直接return,放弃此情况。
    • 其次,如果剩余个数为1,并且加上当前target后能够达到sum值,那么此时的target就是我们需要的最后一个元素,将其加入答案res,加入之后移除末尾元素,以便进行后续运算。
    • 剩余部分请看代码中的注释。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Solution {

// res存放最终答案
List<List<Integer>> res = new ArrayList<>();

// temp存放每一个解情况
List<Integer> temp = new ArrayList<>();

public List<List<Integer>> combinationSum3(int k, int n) {
// 判断此时是否有解
if (n >= (1 + k) * k / 2 && n <= (19 - k) * k / 2) {
calculate(k, n, n, 0);
}
return res;
}

public void calculate(int rest, int target, int sum, int currentSum) {

// 如果此时只剩一个数字,而缺少的和大于9,已无法实现,直接return舍弃
if (rest == 1 && target > 9) {
return;
}

// 如果剩余一个元素,且加上target后当前和currentSum等于要求的sum,则输出
// 第二个条件是为了保证最后一个元素大于倒数第二元素
// (排除诸如[1, 4, 9, 9],[1, 3, 7, 7]的有重复数字的情况)
else if (rest == 1 && temp.get(temp.size() - 1) < target && currentSum + target == sum) {
temp.add(target);
res.add(List.copyOf(temp));
// 移除情况,为后续递归做准备
temp.remove(temp.size() - 1);
}

// 此时的情况为:剩余元素大于1,或者currentSum + target != sum
else {
// 如果是由于后者情况,那么此时为一个错误解,return舍弃
if (rest == 1) {
return;
} else {
// 如果是前者情况,那么此时仍可放入至少两个元素,从当前temp的末尾元素加1开始遍历
// 如果当前temp为空,则从0开始遍历,依次尝试放入
for (int i = (temp.size() == 0 ? 1 : temp.get(temp.size() - 1) + 1); i <= target && i <= 9; i++) {
temp.add(i);
// 放入i后,剩余可放入元素rest减1,新的target变成target - i,当前和currentSum + i
calculate(rest - 1, target - i, sum, currentSum + i);
// 回到此处继续执行,说明最后一个元素的加入后的情况已全部遍历,将此元素移除
temp.remove(temp.size() - 1);
}
}
}
}
}