blog/content/post/算法题/子序列dp.md
2025-09-02 00:39:40 +08:00

181 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: 两字符串系列DP问题归纳
description: 两字符串二维DP系列问题
date: 2025-08-31T21:50:00+08:00
lastmod: 2025-09-02T00:39:00+08:00
slug: 子序列dp
# image: helena-hertz-wWZzXlDpMog-unsplash.jpg
categories:
- 算法题
tags: [
"字符串",
"动态规划",
"算法"
]
math: true
---
约定:记两个字符串`s1``s2`的长度分别为 $m$ 和 $n$,令`vector<vector<int>> dp(m+1, vector<int>(n+1, 0));``dp[i][j]` 表示 `s1[0..i-1]``s2[0..j-1]` 的 (最长公共子序列/不同子序列/……) 的 (长度/个数/……)。
# 最长公共子序列
给定两个字符串 `s1``s2`,返回这两个字符串的最长 **公共子序列** 的长度。如果不存在 **公共子序列** ,返回 `0`
一个字符串的 **子序列** 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
- 例如,`"ace"``"abcde"` 的子序列,但 `"aec"` 不是 `"abcde"` 的子序列。
两个字符串的 **公共子序列** 是这两个字符串所共同拥有的子序列。
## DP公式
`dp[i][j]`代表的是`s1[0..i-1]``s2[0..j-1]`的最长公共子序列的长度。注意到这个问题两个字符串地位是完全对称的所以DP公式中i和j也应当可以互换。
- 如果 $s1[i-1] = s2[j-1]$,则 $dp[i][j] = dp[i-1][j-1] + 1$。
- 如果 $s1[i-1] \ne s2[j-1]$,则 $dp[i][j] = \max(dp[i-1][j], dp[i][j-1])$。
## 边界条件
$dp[0][j] = 0$$dp[i][0] = 0$。
# 最长回文子序列
给你一个字符串 `s` ,找出其中最长的回文子序列,并返回该子序列的长度。
**示例:**
> 输入s = "bbbab"
> 输出4
> 解释:一个可能的最长回文子序列为 "bbbb" 。
此题可以看做**一个字符串和它的逆序字符串的最长公共子序列**。
# 不同的子序列
给你两个字符串 `s1``s2` ,统计并返回在 `s1` 的 子序列 中 `s2` 出现的个数。
**示例:**
> 输入s1 = "rabbbit", s2 = "rabbit"
> 输出3
> 解释:
> 如下所示, 有 3 种可以从 `s1` 中得到 `s2` 的方案。
>- <u>rabb</u>b<u>it</u>
>- <u>rab</u>b<u>bit</u>
>- <u>ra</u>b<u>b</u><u>bit</u>
## DP公式
`dp[i][j]`代表的是`s1[0..i-1]`的子序列中`s2[0..j-1]`的个数。
- 如果 $s1[i-1] = s2[j-1]$,则 $dp[i][j] = dp[i-1][j-1] + dp[i-1][j]$。
- 如果 $s1[i-1] \ne s2[j-1]$,则 $dp[i][j] = dp[i-1][j]$。
## 边界条件
- $dp[0][j] = 0$1 <= j <= n表示在空字符串中找到非空字符串显然不可能。
- $dp[i][0] = 1$0 <= i <= m表示在非空字符串中找到空字符串序列只有一种办法删除所有字符。
# 编辑距离
给你两个单词 `s1``s2` 请返回将 `s1` 转换成 `s2` 所使用的最少操作数。你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
**示例:**
> 输入word1 = "horse", word2 = "ros"
> 输出3
> 解释:
>- horse -> rorse (将 'h' 替换为 'r')
>- rorse -> rose (删除 'r')
>- rose -> ros (删除 'e')
## DP公式
`dp[i][j]`代表的是`s1[0..i-1]`转换成`s2[0..j-1]`所使用的最少操作数。注意到这个问题两个字符串地位也是完全对称的所以DP公式中i和j也应当可以互换。
- 如果 $s1[i-1] = s2[j-1]$,则 $dp[i][j] = dp[i-1][j-1]$。
- 如果 $s1[i-1] \ne s2[j-1]$,则 $dp[i][j] = \min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1$。
## 边界条件
- $dp[0][j] = j$0 <= j <= n表示将空字符串转换成`s2[0..j-1]`,需要插入 $j$ 个字符。
- $dp[i][0] = i$0 <= i <= m表示将`s1[0..i-1]`转换成空字符串,需要删除 $i$ 个字符。
# 两个字符串的最小ASCII删除和
给定两个字符串`s1``s2`,返回 使两个字符串相等所需删除字符的 **ASCII** 值的最小和。
**示例:**
> 输入: s1 = "sea", s2 = "eat"
> 输出: 231
> 解释: 在 "sea" 中删除 "s" 并将 "s" 的值(115)加入总和。
> 在 "eat" 中删除 "t" 并将 116 加入总和。
> 结束时两个字符串相等115 + 116 = 231 就是符合条件的最小和。
## DP公式
`dp[i][j]`代表的是`s1[0..i-1]``s2[0..j-1]`相等所需删除字符的ASCII值的最小和。注意到这个问题两个字符串地位也是完全对称的所以DP公式中i和j也应当可以互换。
- 如果 $s1[i-1] = s2[j-1]$,则 $dp[i][j] = dp[i-1][j-1]$。
- 如果 $s1[i-1] \ne s2[j-1]$,则 $dp[i][j] = \min(dp[i][j-1]+s2[j-1], dp[i-1][j]+s1[i-1])$。
## 边界条件
- $dp[i][0]=dp[i-1][0]+s1[i-1]$0 <= i <= m。
- $dp[0][j]=dp[0][j-1]+s2[j-1]$0 <= j <= n。
# 交错字符串
给定三个字符串 `s1``s2``s3`,请你帮忙验证 `s3` 是否是由 `s1``s2` 交错组成的。
**示例:**
<img src="https://cdn.ember.ac.cn/images/bed/202509011141981.png" width="500">
> 输入s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac"
> 输出true
> 解释s3 可以由 s1 和 s2 交错组成。
> s1 = "aabcc"
> s2 = "dbbca"
> s3 = "aadbbcbcac"
> s3 可以由 s1 和 s2 交错组成。
## DP公式
`dp[i][j]`代表的是`s1[0..i-1]``s2[0..j-1]`能否交错组成`s3[0..i+j-1]`。注意到这个问题两个字符串地位也是完全对称的所以DP公式中i和j也应当可以互换。
- 如果 $s1[i-1] = s3[i+j-1]$,则 $dp[i][j] = dp[i-1][j]$。
- 如果 $s2[j-1] = s3[i+j-1]$,则 $dp[i][j] = dp[i][j-1]$。
- 合起来就是 `dp[i][j] = (dp[i-1][j] && s1[i-1] == s3[i+j-1]) || (dp[i][j-1] && s2[j-1] == s3[i+j-1])`
## 边界条件
- `dp[0][0] = true`,表示空字符串可以交错组成空字符串。
- `dp[i][0] = (dp[i-1][0] && s1[i-1] == s3[i-1]) `1 <= i <= m。
- `dp[0][j] = (dp[0][j-1] && s2[j-1] == s3[j-1]) `1 <= j <= n。
- <strong>必须检查:</strong>如果`s1.size() + s2.size() != s3.size()`,则直接`return false`
# 通配符匹配
给你一个输入字符串 (`s`) 和一个字符模式 (`p`) ,请你实现一个支持 `'?'``'*'` 匹配规则的通配符匹配:
- `'?'` 可以匹配任何单个字符。
- `'*'` 可以匹配任意字符序列(包括空字符序列)。
判定匹配成功的充要条件是:字符模式必须能够 **完全匹配** 输入字符串(而不是部分匹配)。
## DP公式
`dp[i][j]`代表的是`s[0..i-1]``p[0..j-1]`能否匹配(`bool`类型)。
- 如果 $p[j-1] = '?'$ 或者 $p[j-1] = s[i-1]$,则 $dp[i][j] = dp[i-1][j-1]$。
- 如果 $p[j-1] = '*'$,则 $dp[i][j] = dp[i-1][j]\ ||\ dp[i][j-1]$。
- 其他情况下,$dp[i][j] = false$。
## 边界条件
- `dp[0][0] = true`,表示空字符串可以匹配空字符串。
- `dp[i][0] = false`1 <= i <= m表示在非空字符串中匹配空字符串显然不可能。
- `dp[0][j]``true`当且仅当`p[0..j-1]`中全是`'*'`1 <= j <= n。