最优包含
题目描述
我们称一个字符串 SS 包含字符串 TT 是指 TT 是 SS 的一个子序列,即可以从字符串 SS 中抽出若干个字符,它们按原来的顺序组合成一个新的字符串与 TT 完全一样。
给定两个字符串 SS 和 TT,请问最少修改 SS 中的多少个字符,能使 SS 包含 TT ?
其中,1≤∣T∣≤∣S∣≤10001≤∣T∣≤∣S∣≤1000。
输入描述
输入两行,每行一个字符串。
第一行的字符串为 SS,第二行的字符串为 TT。
两个字符串均非空而且只包含大写英文字母。
输出描述
输出一个整数,表示答案。
输入输出样例
示例
输入
ABCDEABCD
XAABZ
输出
3
运行限制
- 最大运行时间:1s
- 最大运行内存: 256M
总通过次数: 1879 | 总提交次数: 2244 | 通过率: 83.7%
难度: 中等 标签: 2019, 国赛, 动态规划
方法思路
这个问题要求我们找到最少需要修改字符串S中的多少个字符,使得字符串T成为S的子序列。关键在于动态规划的应用,通过构建一个二维数组来记录状态转移,从而高效地解决问题。
动态规划思路:
状态定义: 定义
dp[i][j]
表示考虑S的前i个字符和T的前j个字符时,最少需要修改的字符数目,使得T的前j个字符是S的前i个字符的子序列。初始化:
dp[0][0] = 0
表示两个空字符串不需要任何修改。对于
i > 0
且j = 0
的情况,dp[i][0] = 0
,因为T为空时不需要任何字符。对于
j > 0
且i = 0
的情况,dp[0][j] = INF
(无穷大),因为S为空时无法包含非空的T。
状态转移:
如果
S[i-1] == T[j-1]
,则dp[i][j] = dp[i-1][j-1]
,即当前字符匹配,无需修改。否则,
dp[i][j] = min(dp[i-1][j-1] + 1, dp[i-1][j])
,即可以选择修改当前字符(增加修改次数)或者不匹配当前字符(保持之前的修改次数)。
结果提取: 最终的结果是
dp[m][n]
,其中m和n分别是S和T的长度。我们需要在所有可能的起始位置中找到最小的修改次数,即遍历dp[i][n]
,其中i
从n
到m
。#include <iostream> #include <vector> #include <string> #include <algorithm> using namespace std; int main() { string S, T; cin >> S >> T; int m = S.size(); int n = T.size(); // dp[i][j] 表示S的前i个字符包含T的前j个字符所需的最小修改次数 vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0)); // 初始化:当T为空串时,不需要任何修改 for (int i = 0; i <= m; ++i) { dp[i][0] = 0; } // 初始化:当S为空串且T非空时,无法包含,设为无穷大(这里用m+1代替,因为最大修改次数不会超过m) for (int j = 1; j <= n; ++j) { dp[0][j] = m + 1; // 表示不可达 } for (int i = 1; i <= m; ++i) { for (int j = 1; j <= n; ++j) { if (S[i - 1] == T[j - 1]) { dp[i][j] = dp[i - 1][j - 1]; } else { dp[i][j] = min(dp[i - 1][j - 1] + 1, dp[i - 1][j]); } } } // 在S的所有可能位置中寻找最小的修改次数 int result = m + 1; for (int i = n; i <= m; ++i) { result = min(result, dp[i][n]); } cout << result << endl; return 0; }
代码解释
输入处理: 读取两个字符串S和T。
初始化动态规划数组:
dp[i][j]
用于存储状态,初始时处理边界条件(空字符串的情况)。状态转移: 遍历S和T的每个字符,根据字符是否匹配更新状态。若字符匹配,则直接继承之前的状态;否则,选择修改当前字符或跳过当前字符中的较小值。
结果提取: 在所有可能的结束位置中查找最小的修改次数,确保T是S的子序列。
输出结果: 打印最少需要修改的字符数目。