From 7e43421a71e6ccca1224b31a65787e04f2a25caa Mon Sep 17 00:00:00 2001 From: Sainnhe Park Date: Fri, 10 Feb 2023 17:00:23 +0800 Subject: [PATCH] Update subsequence --- notes/src/subsequence.md | 90 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/notes/src/subsequence.md b/notes/src/subsequence.md index 0f315e4..cd20322 100644 --- a/notes/src/subsequence.md +++ b/notes/src/subsequence.md @@ -1,5 +1,13 @@ # 子序列问题 +子序列问题一般涉及到两个序列 `s` 和 `t`,`dp[i][j]` 一般设计成 `i` 和 `j` 为这两个序列的索引。 + +我们需要讨论的是当 `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. 最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/) - `dp[i]` 表示以 `nums[i]` 结尾的最长递增子序列的长度 @@ -31,8 +39,88 @@ - `dp[i][j]` 表示以 `i` 为下标结尾的 text1 和以 `j` 为下标结尾的 text2 的最长公共子序列 - 递推公式 - `if (text1[i] == text2[j]) dp[i][j] = dp[i - 1][j - 1] + 1` - - `else dp[i][j] = max(dp[i - 1][j], dp[i][j - 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. 最大子序和](https://leetcode.cn/problems/maximum-subarray/) + +- `dp[i]` 为包括下标 `i` 的最大连续子序列和 +- `max(dp[i - 1] + nums[i], nums[i])` +- `dp[0] = nums[0]` +- 从前往后 + +## [115. 不同的子序列](https://leetcode.cn/problems/distinct-subsequences/) + +- `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. 两个字符串的删除操作](https://leetcode.cn/problems/delete-operation-for-two-strings/) + +- `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. 编辑距离](https://leetcode.cn/problems/edit-distance/) + +- `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. 回文子串](https://leetcode.cn/problems/palindromic-substrings/) + +- `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. 最长回文子序列](https://leetcode.cn/problems/longest-palindromic-subsequence/) + +字串要连续,子序列不用连续。 + +- `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`