# [39] 组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

输入:candidates = [2,3,6,7], target = 7

输出:[[2,2,3],[7]]

解释:

2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。

7 也是一个候选, 7 = 7 。

仅有这两种组合。

示例 2:

输入:candidates = [2,3,5], target = 8

输出:[[2,2,2,2],[2,3,3],[3,5]]

示例 3:

输入:candidates = [2], target = 1

输出:[]

提示:

1 <= candidates.length <= 30

1 <= candidates[i] <= 200

candidate 中的每个元素都 互不相同

1 <= target <= 500

这道题用到的是经典的回溯法,解题思路类似于两数之和,记录下离目标值的差值,遍历可用数字加入集合中即可。

难点在于如何让返回结果中的元素集合不重复。

除开建立 hashMap 或者 Set 这样的方式,一般来说,去重的方法基本上都伴随着排序,因为排序能将重复元素聚集在一起,方便剪枝。

我们可以在每次进行子节点递归时,记录当前 index。由于 candidates 已经过排序,因此每一次取值都不会比 path 内中所有元素小,从而避免重复集合的问题。

function combinationSum(candidates: number[], target: number): number[][] {
  candidates.sort((a, b) => a - b);
  const res: number[][] = [];
  backtrack(0, [], target);
  return res;

  function backtrack(start: number, path: number[], rest: number) {
    if (rest === 0) {
      res.push([...path]);
    }

    // 由于已排序,每一次取值都不会比 path 内中所有元素小,从而避免重复组合的问题
    for (let i = start; i < candidates.length; i++) {
      // 如果剩余值比当前选择小,则跳过
      if (rest < candidates[i]) {
        break;
      }
      path.push(candidates[i]);
      backtrack(i, path, rest - candidates[i]);
      path.pop();
    }
  }
}