LeetCode组合问题小合集

304 阅读1分钟

第一题 39. Combination Sum

回溯即可。

var combinationSum = function(candidates, target) {
  const res = [];
  dfs(0,[],0);
  return res;

  function dfs(idx,tempArr,sum) {
    if (sum===target) res.push(tempArr);
    if (sum>=target||idx>=candidates.length) return;
    dfs(idx+1,tempArr.slice(),sum);
    for (let cnt=1; sum+candidates[idx]<=target; cnt++) {
      tempArr.push(candidates[idx]);
      sum += candidates[idx];
      dfs(idx+1,tempArr.slice(),sum);
    }
  }
};

第二题 40. Combination Sum II

一种思路是参考上一题。上一题中没有对每个元素使用次数做限制,这一题中对每个元素使用次数做了限制,一个元素的最多使用次数为该元素在candidates数组中的出现次数。因此我们统计出每个元素的出现次数,然后在dfs中对该元素遍历过程中令cnt不超过该出现次数。

var combinationSum2 = function(candidates, target) {
  const num_cnt = {};
  for (const num of candidates) {
    if (num in num_cnt) num_cnt[num]++;
    else num_cnt[num] = 1;
  }
  const nums = Object.keys(num_cnt);
  const res = [];
  dfs(0,[],0);
  return res;

  function dfs(idx,tempArr,sum) {
    if (sum===target) res.push(tempArr.slice());
    if (sum>=target||idx>=nums.length) return;
    dfs(idx+1,tempArr.slice(),sum);
    for (let cnt=1; cnt<=num_cnt[nums[idx]]&&sum<=target; cnt++) {
      sum += parseInt(nums[idx]);
      tempArr.push(parseInt(nums[idx]));
      dfs(idx+1,tempArr.slice(),sum);
    }
  }
};

另一种思路,对于dfs的每一层,都看作从当前idx开始往后统计target-sum的集合。因此我们在dfs的每一层中对i遍历时,如果candidates[i]===candidates[i-1],则说明以candidates[i]这个数开头的所有可能的组合结果都遍历过了,因此直接跳过。

var combinationSum2 = function(candidates, target) {
  candidates.sort((a,b)=>a-b);
  const res = [];
  dfs(0,[],0);
  return res;

  function dfs(idx,tempArr,sum) {
    if (sum===target) res.push(tempArr.slice());
    if (sum>=target||idx>=candidates.length) return;
    for (let i = idx; i < candidates.length; i++) {
      if (i>idx&&candidates[i]===candidates[i-1]) continue;
      dfs(i+1,tempArr.concat([candidates[i]]),sum+candidates[i]);
    }
  }
};

第三题 216. Combination Sum III

思路很清晰,与上一题40. Combination Sum II很像。

var combinationSum3 = function(k, n) {
  const res = [];
  dfs(1,[],0);
  return res;

  function dfs(num,tempArr,sum) {
    if (sum===n&&tempArr.length===k) res.push(tempArr.slice());
    if (sum>=n||tempArr.length>=k||num>9) return;
    for (let i = num; i <= 9&&sum+i <= n; i++) {
      dfs(i+1,tempArr.concat([i]),sum+i);
    }
  }
};

第四题 377. Combination Sum IV

这一题,如果继续使用dfs会超时。而且这题只要求计算组合种数,没要求列出所有可能的组合情况,因此我们考虑使用动态规划。令dp[t]表示target等于t时的组合种数,dp[t]=sum(dp[t-num])。

var combinationSum4 = function(nums, target) {
  const dp = new Array(target+1).fill(0);
  nums.sort((a,b)=>a-b);
  dp[0] = 1;
  for (let t = 1; t <= target; t++) {
    for (const num of nums) {
      if (num>t) break;
      dp[t] += dp[t-num];
    }
  }
  return dp[target];
};

总结

从上可以看出,对于组合类型问题,大多使用dfs回溯,特别是对于需要列出所有组合情况的问题,一般这类题的数据量不会太大。如果问题变成了求组合种数,且数据量较大的情况,我们一般考虑使用动态规划解决。