返回

「前端刷题」39. 组合总和:踏上数字组合的探索之旅

前端

在计算机科学领域,组合总和是一个经典的动态规划问题。给定一个正整数数组candidates和一个正整数target,我们需要在candidates中找出所有可以使数字和为target的组合。

动态规划算法解析

// 方法一:回溯搜索法
function combinationSum(candidates, target) {
  // 结果数组
  const result = [];
  // 回溯函数
  const backtrack = (index, currentCombination, currentSum) => {
    // 如果超过目标值,则直接返回
    if (currentSum > target) {
      return;
    }
    // 如果等于目标值,则将当前组合加入结果数组
    if (currentSum === target) {
      result.push([...currentCombination]);
      return;
    }
    // 如果还没有达到目标值,则继续回溯
    for (let i = index; i < candidates.length; i++) {
      // 将当前数字加入当前组合
      currentCombination.push(candidates[i]);
      // 计算当前和
      currentSum += candidates[i];
      // 递归调用回溯函数
      backtrack(i, currentCombination, currentSum);
      // 从当前组合中移除当前数字
      currentCombination.pop();
      // 将当前和减去当前数字
      currentSum -= candidates[i];
    }
  };
  // 从第一个数字开始回溯
  backtrack(0, [], 0);
  return result;
}

这个算法的时间复杂度是O(2^n),其中n是candidates数组的长度。这是因为对于每个数字,我们都有两个选择:选择它或不选择它。因此,对于n个数字,就有2^n种可能的组合。

// 方法二:动态规划法
function combinationSum(candidates, target) {
  // 创建一个二维数组dp,dp[i][j]表示使用candidates[0]到candidates[i-1]这些数字能否凑出j这个数
  const dp = new Array(candidates.length + 1).fill(0).map(() => new Array(target + 1).fill(false));
  // 初始化dp[0][0]为true,表示空集可以凑出0这个数
  dp[0][0] = true;
  // 遍历candidates数组
  for (let i = 1; i <= candidates.length; i++) {
    // 遍历target数组
    for (let j = 1; j <= target; j++) {
      // 如果不使用candidates[i-1]这个数字,则dp[i][j]等于dp[i-1][j]
      dp[i][j] = dp[i - 1][j];
      // 如果使用candidates[i-1]这个数字,则dp[i][j]等于dp[i-1][j-candidates[i-1]]
      if (j >= candidates[i - 1]) {
        dp[i][j] |= dp[i - 1][j - candidates[i - 1]];
      }
    }
  }
  // 结果数组
  const result = [];
  // 从后往前遍历dp数组,找到所有为true的dp[i][j]
  for (let i = candidates.length; i >= 1; i--) {
    for (let j = target; j >= 0; j--) {
      // 如果dp[i][j]为true,则表示使用candidates[0]到candidates[i-1]这些数字可以凑出j这个数
      if (dp[i][j]) {
        // 将candidates[i-1]加入结果数组
        result.push(candidates[i - 1]);
        // 将j减去candidates[i-1]
        j -= candidates[i - 1];
        // 继续从后往前遍历dp数组,找到下一个为true的dp[i][j]
      }
    }
  }
  return result;
}

这个算法的时间复杂度是O(n * target),其中n是candidates数组的长度,target是目标值。这是因为对于每个数字,我们都需要检查它是否能和前面的数字组合起来凑出目标值。

复杂度分析

在第一种方法中,我们使用回溯搜索法来解决组合总和问题。回溯搜索法的复杂度为O(2^n),其中n是candidates数组的长度。这是因为对于每个数字,我们都有两个选择:选择它或不选择它。因此,对于n个数字,就有2^n种可能的组合。

在第二种方法中,我们使用动态规划法来解决组合总和问题。动态规划法的复杂度为O(n * target),其中n是candidates数组的长度,target是目标值。这是因为对于每个数字,我们都需要检查它是否能和前面的数字组合起来凑出目标值。

结语

组合总和问题是一个经典的动态规划问题,它可以有多种不同的解法。我们介绍了两种常见的解法:回溯搜索法和动态规划法。这两种方法各有优劣,回溯搜索法的时间复杂度较高,但易于理解和实现;动态规划法的时间复杂度较低,但实现起来比较复杂。