动态规划(Dynamic Programming, DP)在字符串数组相关的算法题中应用广泛,尤其是在解决子序列、子串、编辑距离、匹配等问题时。动态规划的核心思想是将问题分解为子问题,并通过存储子问题的解来避免重复计算,从而提高效率。
文章目录
1143. Longest Common Subsequence
Given two strings text1
and text2
, return the length of their longest common subsequence. If there is no common subsequence, return 0.
A subsequence of a string is a new string generated from the original string with some characters (can be none) deleted without changing the relative order of the remaining characters.
For example, "ace"
is a subsequence of "abcde"
.
A common subsequence of two strings is a subsequence that is common to both strings.
Example 1:
Input: text1 = "abcde", text2 = "ace"
Output: 3
Explanation: The longest common subsequence is "ace" and its length is 3.
Example 2:
Input: text1 = "abc", text2 = "abc"
Output: 3
Explanation: The longest common subsequence is "abc" and its length is 3.
Example 3:
Input: text1 = "abc", text2 = "def"
Output: 0
Explanation: There is no such common subsequence, so the result is 0.
问题描述
给定两个字符串 s1
和 s2
,找到它们的最长公共子序列的长度。子序列是指从原字符串中删除一些字符(可以不连续)后得到的新字符串。
解题思路
- 定义状态:
dp[i][j]
表示s1
的前i
个字符和s2
的前j
个字符的最长公共子序列长度。 - 状态转移:
- 如果
s1[i-1] == s2[j-1]
,则dp[i][j] = dp[i-1][j-1] + 1
。 - 否则,
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
。
- 如果
- 初始化:
dp[0][j] = 0
和dp[i][0] = 0
,表示空字符串的 LCS 长度为 0。 - 结果:
dp[m][n]
,其中m
和n
分别是s1
和s2
的长度。
C++ 实现
int longestCommonSubsequence(string s1, string s2) {
int m = s1.length(), n = s2.length();
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (s1[i - 1] == s2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
1092. Shortest Common Supersequence
Given two strings str1
and str2
, return the shortest string that has both str1
and str2
as subsequences. If there are multiple valid strings, return any of them.
A string s
is a subsequence of string t
if deleting some number of characters from t
(possibly 0) results in the string s
.
Example 1:
Input: str1 = "abac", str2 = "cab"
Output: "cabac"
Explanation:
str1 = "abac" is a subsequence of "cabac" because we can delete the first "c".
str2 = "cab" is a subsequence of "cabac" because we can delete the last "ac".
The answer provided is the shortest such string that satisfies these properties.
Example 2:
Input: str1 = "aaaaaaaa", str2 = "aaaaaaaa"
Output: "aaaaaaaa"
解题思路
这个问题可以转化为求两个字符串的最长公共子序列(LCS),然后通过 LCS 构造最短的公共超序列。
构造最短公共超序列
- 初始化指针:
- 使用指针
i
和j
分别遍历str1
和str2
。
- 使用指针
- 遍历字符串:
- 如果
str1[i] == str2[j]
,则将当前字符添加到结果中,并移动两个指针。 - 否则,将
str1[i]
或str2[j]
中较小的字符添加到结果中,并移动对应的指针。
- 如果
- 处理剩余字符:
- 如果
str1
或str2
有剩余字符,将它们全部添加到结果中。
- 如果
C++ 实现
string shortestCommonSupersequence(string str1, string str2) {
int m = str1.length(), n = str2.length();
// 动态规划求 LCS
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (str1[i - 1] == str2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
// 构造最短公共超序列
string result;
int i = m, j = n;
while (i > 0 && j > 0) {
if (str1[i - 1] == str2[j - 1]) {
result.push_back(str1[i - 1]);
i--;
j--;
} else if (dp[i - 1][j] > dp[i][j - 1]) {
result.push_back(str1[i - 1]);
i--;
} else {
result.push_back(str2[j - 1]);
j--;
}
}
// 处理剩余字符
while (i > 0) {
result.push_back(str1[i - 1]);
i--;
}
while (j > 0) {
result.push_back(str2[j - 1]);
j--;
}
// 反转结果
reverse(result.begin(), result.end());
return result;
}