返回

解析 LeetCode 115:探究子序列计数的奥秘,掌握动态规划精髓

前端

算法思想:动态规划的魅力

动态规划是一种求解最优化问题的有力工具,它将大问题分解成一系列子问题,并通过求解这些子问题来逐步解决大问题。在解决 LeetCode 115 时,我们可以使用动态规划来求解子序列计数问题。

具体而言,我们可以定义一个二维数组 dp,其中 dp[i][j] 表示字符串 s 的前 i 个字符中包含字符串 t 的前 j 个字符的子序列的个数。我们使用动态规划来计算 dp 数组,具体过程如下:

  1. 初始化:设置 dp[0][0] = 1,因为空字符串中包含空子序列的个数为 1。
  2. 递推关系:对于每个 ij,如果 s[i] == t[j],则 dp[i][j] = dp[i-1][j-1] + dp[i-1][j],否则 dp[i][j] = dp[i-1][j]
  3. 边界条件:当 ij 为 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. 动态规划的递推关系是如何推导出来的?

对于每个 ij,如果 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 分别是字符串 st 的长度。这是因为 dp 数组的每个元素都需要计算一次,并且每个元素的计算需要 O(1) 的时间。