返回

逐层递进,详解 Leetcode 39:组合总和(Python)中的动态规划

后端

引言

在计算机科学中,动态规划是一种优化算法技术,用于解决具有重叠子问题和最优子结构的复杂问题。它将问题拆分成更小的子问题,然后逐层解决这些子问题,并将结果存储起来,避免重复计算。

问题

Leetcode 39:组合总和是一个经典的组合问题。给定一个无重复元素的数组 candidates 和一个目标和 target,求出所有和为 target 的不同组合。

动态规划求解

为了解决此问题,我们将使用动态规划技术。首先,定义一个二维表 dp,其中 dp[i][j] 表示使用 candidates 中前 i 个元素和为 j 的组合数量。

初始化

我们从 base case 开始,即 dp[0][0] = 1,因为空集与和为 0 的组合相同。

状态转移

对于所有其他情况,我们有两种选择:

  1. 不使用第 i 个元素:dp[i][j] = dp[i-1][j]
  2. 使用第 i 个元素:dp[i][j] += dp[i-1][j - candidates[i]]

这意味着,对于给定的 ijdp[i][j] 是不使用第 i 个元素和使用第 i 个元素的组合数量之和。

结果计算

最后,dp[candidates.length][target] 就是所有和为 target 的不同组合的数量。

代码实现

以下是使用 Python 实现的动态规划解决方案:

def combinationSum(candidates, target):
  """
  :type candidates: List[int]
  :type target: int
  :rtype: List[List[int]]
  """
  # 初始化二维表
  dp = [[0] * (target + 1) for _ in range(len(candidates) + 1)]

  # 初始化 base case
  dp[0][0] = 1

  # 状态转移
  for i in range(1, len(candidates) + 1):
    for j in range(1, target + 1):
      dp[i][j] = dp[i - 1][j]
      if j - candidates[i] >= 0:
        dp[i][j] += dp[i - 1][j - candidates[i]]

  # 获取所有组合
  res = []
  def backtrack(i, j, path):
    if j == 0:
      res.append(path)
    elif i > 0 and j - candidates[i] >= 0:
      backtrack(i - 1, j, path)
      backtrack(i - 1, j - candidates[i], path + [candidates[i]])

  backtrack(len(candidates), target, [])

  return res

示例

对于输入 candidates = [2,3,6,7], target = 7,动态规划表如下:

i/j 0 1 2 3 4 5 6 7
0 1 0 0 0 0 0 0 0
1 1 0 0 0 0 0 0 0
2 1 0 1 0 0 0 0 0
3 1 0 1 1 0 0 0 0
4 1 0 1 1 1 0 0 0
5 1 0 1 1 1 1 0 0
6 1 0 1 1 1 1 1 0
7 1 0 1 1 1 1 1 2

最后,dp[candidates.length][target] = 2,表示有两种组合可以和为 7,分别是 [2, 2, 3][7]

总结

通过使用动态规划,我们将 Leetcode 39:组合总和的复杂度从 O(2^n) 优化到了 O(n * target),显著提高了效率。动态规划是一种强大的技术,可用于解决各种复杂问题,尤其是在存在重叠子问题和最优子结构时。