返回
解析 LeetCode 115:探究子序列计数的奥秘,掌握动态规划精髓
前端
2023-12-12 16:52:11
算法思想:动态规划的魅力
动态规划是一种求解最优化问题的有力工具,它将大问题分解成一系列子问题,并通过求解这些子问题来逐步解决大问题。在解决 LeetCode 115 时,我们可以使用动态规划来求解子序列计数问题。
具体而言,我们可以定义一个二维数组 dp
,其中 dp[i][j]
表示字符串 s
的前 i
个字符中包含字符串 t
的前 j
个字符的子序列的个数。我们使用动态规划来计算 dp
数组,具体过程如下:
- 初始化:设置
dp[0][0] = 1
,因为空字符串中包含空子序列的个数为 1。 - 递推关系:对于每个
i
和j
,如果s[i] == t[j]
,则dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
,否则dp[i][j] = dp[i-1][j]
。 - 边界条件:当
i
或j
为 0 时,将对应的dp
值设置为 0。
Python 代码实现:简洁高效
def numDistinct(s, t):
# 初始化 dp 数组
m, n = len(s), len(t)
dp = [[0] * (n + 1) for _ in range(m + 1)]
# 初始化第一行和第一列
for i in range(m + 1):
dp[i][0] = 1
# 递推计算 dp 数组
for i in range(1, m + 1):
for j in range(1, n + 1):
if s[i-1] == t[j-1]:
dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
else:
dp[i][j] = dp[i-1][j]
# 返回结果
return dp[m][n]
常见问题解答:融会贯通
1. 如何理解子序列的定义?
子序列是指通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。例如,"ACE" 是 "ABCDE" 的一个子序列,而 "AEC" 不是。
2. 动态规划的递推关系是如何推导出来的?
对于每个 i
和 j
,如果 s[i] == t[j]
,则 dp[i][j]
表示字符串 s
的前 i
个字符中包含字符串 t
的前 j
个字符的子序列的个数。由于有两种情况:要么包含 s[i]
,要么不包含 s[i]
,因此递推关系为 dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
。如果 s[i] != t[j]
,则递推关系为 dp[i][j] = dp[i-1][j]
。
3. 如何避免重复计算?
使用动态规划可以避免重复计算。由于 dp
数组记录了之前计算的结果,因此在计算 dp[i][j]
时,可以直接使用 dp[i-1][j-1]
和 dp[i-1][j]
的值,而无需重新计算。
4. 动态规划的时间复杂度是多少?
动态规划的时间复杂度为 O(mn),其中 m 和 n 分别是字符串 s
和 t
的长度。这是因为 dp
数组的每个元素都需要计算一次,并且每个元素的计算需要 O(1) 的时间。