本周的算法学习集中在字符串处理上,以最长回文子序列、字符串的编辑距离和正则表达式匹配这三个问题为例。这些问题不仅锻炼了我对字符串操作的技巧,也是算法竞赛和面试中的热点。
最长回文子序列
最长回文子序列问题要求找出给定字符串中最长的回文子序列。动态规划是解决这个问题的关键。
- 状态定义:
dp[i][j]表示从索引i到j的子串中最长回文子序列的长度。 - 状态转移:如果
s[i] == s[j],dp[i][j] = dp[i + 1][j - 1] + 2;否则,dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])。 - 初始化:
dp[i][i] = 1,因为单个字符自身是一个长度为1的回文子序列。 - 结果:
dp[0][n - 1]存储了整个字符串的最长回文子序列长度。
C++ 代码实现
#include <vector> using namespace std;
int longestPalindromeSubseq(const string &s) { int n = s.length();
vector<vector<int>> dp(n, vector<int>(n, 0));
for (int i = n - 1; i >= 0; --i) { dp[i][i] = 1;
for (int j = i + 1; j < n; ++j) {
if (s[i] == s[j]) {
dp[i][j] = dp[i + 1][j - 1] + 2;
} else {
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
}
}
}
return dp[0][n - 1];
}
字符串的编辑距离
关键思路
编辑距离问题,又称Levenshtein距离,是计算两个字符串之间,由一个转换成另一个所需的最少编辑操作次数。
- 状态定义:
dp[i][j]表示将字符串str1的前i个字符转换为str2的前j个字符所需的最少操作数。 - 状态转移:
dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + (str1[i - 1] != str2[j - 1]))。 - 初始化:
dp[0][j] = j,dp[i][0] = i。
C++ 代码实现
#include <vector> using namespace std;
int minDistance(const string &str1, const string &str2) {
int m = str1.length(), n = str2.length();
vector<vector<int>> dp(m + 1, vector<int>(n + 1));
for (int i = 0; i <= m; ++i)
dp[i][0] = i;
for (int j = 0; j <= n; ++j)
dp[0][j] = j;
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];
} else {
dp[i][j] = min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]}) + 1;
}
}
}
return dp[m][n];
} // 其他函数实现...`
正则表达式匹配
关键思路
正则表达式匹配是判断一个字符串是否符合某种模式。
- 递归方法:将问题分解为子问题,考虑字符匹配和
*的两种情况。 - 贪心匹配:尽可能多地匹配字符。
- 回溯:当贪心匹配失败时,回溯到上一个状态。
C++ 代码实现
#include <vector> using namespace std;
bool isMatch(const string &s, const string &p) {
vector<vector<bool>> dp(s.length() + 1, vector<bool>(p.length() + 1, false));
dp[0][0] = true;
for (int j = 1; j <= p.length(); ++j) {
if (p[j - 1] == '*') {
dp[0][j] = dp[0][j - 2];
}
}
for (int i = 1; i <= s.length(); ++i) {
for (int j = 1; j <= p.length(); ++j) {
if (s[i - 1] == p[j - 1] || p[j - 1] == '.') {
dp[i][j] = dp[i - 1][j - 1];
} else if (p[j - 1] == '*') {
dp[i][j] = dp[i][j - 2] || (dp[i - 1][j] && (s[i - 1] == p[j - 2] || p[j - 2] == '.'));
}
}
}
return dp[s.length()][p.length()];
}
结语
通过本周对字符串问题的深入学习,了解到处理字符串问题,同样可以用之前的动态规划和递归等算法来解决。这些算法不仅提高了编程能力,也为解决实际问题提供了强大的工具。