返回
逐层递进,详解 Leetcode 39:组合总和(Python)中的动态规划
后端
2023-10-26 02:13:21
引言
在计算机科学中,动态规划是一种优化算法技术,用于解决具有重叠子问题和最优子结构的复杂问题。它将问题拆分成更小的子问题,然后逐层解决这些子问题,并将结果存储起来,避免重复计算。
问题
Leetcode 39:组合总和是一个经典的组合问题。给定一个无重复元素的数组 candidates
和一个目标和 target
,求出所有和为 target
的不同组合。
动态规划求解
为了解决此问题,我们将使用动态规划技术。首先,定义一个二维表 dp
,其中 dp[i][j]
表示使用 candidates
中前 i
个元素和为 j
的组合数量。
初始化
我们从 base case 开始,即 dp[0][0] = 1
,因为空集与和为 0 的组合相同。
状态转移
对于所有其他情况,我们有两种选择:
- 不使用第
i
个元素:dp[i][j] = dp[i-1][j]
- 使用第
i
个元素:dp[i][j] += dp[i-1][j - candidates[i]]
这意味着,对于给定的 i
和 j
,dp[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),显著提高了效率。动态规划是一种强大的技术,可用于解决各种复杂问题,尤其是在存在重叠子问题和最优子结构时。