leetcode/notes/src/subsequence.md

5.2 KiB
Raw Blame History

子序列问题

子序列问题一般涉及到两个序列 stdp[i][j] 一般设计成 ij 为这两个序列的索引。

我们需要讨论的是当 s[i] == t[j]s[i] != t[j] 时,如何由以下三个推导出 dp[i][j]

  1. dp[i - 1][j - 1]
  2. dp[i - 1][j]
  3. dp[i][j - 1]

300. 最长递增子序列

  • dp[i] 表示以 nums[i] 结尾的最长递增子序列的长度
  • 遍历 j0i, if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1)
  • dp[i] = 1
  • 外层遍历 i 内层遍历 j,都是从前往后

674. 最长连续递增序列

  • dp[i] 表示以 nums[i] 结尾的最长连续递增子序列的长度
  • 递推公式:
    • if (nums[i] > nums[i - 1]) dp[i] = dp[i - 1] + 1
    • if (nums[i] <= nums[i - 1]) dp[i] = 1
  • dp[0] = 1
  • 从前往后

718. 最长重复子数组

  • dp[i][j] 表示以 i 为下标结尾的 A 和以 j 为下标结尾的 B 的最长重复子数组
  • if (nums1[i] == nums2[j]) dp[i][j] = dp[i - 1][j - 1] + 1 否则为 0
  • 初始化:
    • if (nums1[i] == nums2[0]) dp[i][0] = 1 否则为 0
    • if (nums1[0] == nums2[j]) dp[0][j] = 1 否则为 0
    • 其它都初始化为 0
  • 都从前往后遍历,下标从 1 开始

1143. 最长公共子序列

  • dp[i][j] 表示以 i 为下标结尾的 text1 和以 j 为下标结尾的 text2 的最长公共子序列
  • 递推公式
    • if (text1[i] == text2[j]) dp[i][j] = dp[i - 1][j - 1] + 1
    • if (text1[i] != text2[j]) dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
  • 只需要初始化三个:
    • dp[0][0]
    • dp[1][0]
    • dp[0][1]

53. 最大子序和

  • dp[i] 为包括下标 i 的最大连续子序列和
  • max(dp[i - 1] + nums[i], nums[i])
  • dp[0] = nums[0]
  • 从前往后

115. 不同的子序列

  • dp[i][j] 表示在 s[...i]t[...j] 出现的的次数
  • 递推公式:
    • if (s[i] == t[j])
      • 第一种组合方式是在 s[...i-1]t[...j-1] 出现了 dp[i - 1][j - 1] 次,这时候我们把末尾的 s[i]t[j] 分别加上去,之前的每一次匹配依然有效,所以出现次数为 dp[i - 1][j - 1]
      • 第二种组合方式是,t[...j] 有可能本身就在 s[...i-1] 中出现过,出现的次数是 dp[i - 1][j]
      • 把这两种情况的数量加起来就是 dp[i][j] 了:dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]
    • if (s[i] != t[j])
      • 只有一种可能,那就是 t[...j] 本身就在 s[...i-1] 中出现过,出现次数为 dp[i - 1][j]
  • 需要初始化两个:
    • dp[i][0]
    • dp[0][j]
  • 都从前往后遍历

583. 两个字符串的删除操作

  • dp[i][j] 表示 word1[...i]word2[...j] 所需的操作数
  • 递推公式:
    • if (word1[i] == word2[j]) dp[i][j] = dp[i - 1][j - 1]
    • if (word1[i] == word2[j]) dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1)
  • 初始化
    • dp[i][0] = i
    • dp[0][j] = j
  • 从前往后遍历

72. 编辑距离

  • dp[i][j] 是把 word1[...i] 转换成 word2[...j] 所需的最少操作数
  • 递推公式:
    • if (word1[i] == word2[j]) dp[i][j] = dp[i - 1][j - 1]
    • if (word1[i] != word2[j])
      • dp[i - 1][j - 1] 转换过去,需要把 word1[i] 替换成 word2[j],也就是 dp[i - 1][j - 1] + 1 步操作
      • dp[i][j - 1] 转换过去,需要在 word1[...i] 的末尾插入一个 word2[j],也就是 dp[i][j - 1] + 1 步操作
      • dp[i - 1][j] 转换过去,需要删除 word1[i],也就是 dp[i - 1][j] + 1 步操作
      • 这三种情况取最小,即 min(dp[i - 1][j - 1] + 1, dp[i][j - 1] + 1, dp[i - 1][j] + 1)
  • 初始化:
    • dp[i][0]
    • dp[0][j]
  • 从前向后遍历

647. 回文子串

  • dp[i][j] 表示 s[i...j] 是否是回文串
  • 递推公式:
    • if (s[i] == s[j])
      • if (i == j) dp[i][j] = true
      • else if (j = i + 1) dp[i][j] = true
      • else dp[i][j] = dp[i + 1][j - 1]
    • if (s[i] != s[j]) dp[i][j] = false
  • 全部初始化为 false
  • 遍历顺序:
    • i 从后往前,范围是 0...len-1
    • j 从前往后,范围是 i...len-1

516. 最长回文子序列

字串要连续,子序列不用连续。

  • dp[i][j] 表示 s[i...j] 的最长回文子序列的长度
  • 递推公式:
    • if (s[i] != s[j])
      • if (j = i + 1) dp[i][j] = 0
      • else dp[i][j] = dp[i + 1][j - 1]
    • if (s[i] == s[j])
      • if (i == j) dp[i][j] = 1
      • else if (j = i + 1) dp[i][j] = 2
      • else dp[i][j] = dp[i + 1][j - 1] + 2
  • 全部初始化为 0
  • 遍历顺序:
    • i 从后往前,范围是 0...len-1
    • j 从前往后,范围是 i...len-1