1.字符串编辑
字符串编辑问题是一类常见的问题,通常涉及对字符串进行插入、删除、替换等操作,以达到某种特定的目标。
常见的字符串编辑问题包括:
- 编辑距离(Edit Distance):给定两个字符串,通过插入、删除和替换操作,计算将一个字符串转换为另一个字符串所需的最小操作次数。编辑距离可以用于拼写纠错、DNA序列比对等领域。
- 最长公共子序列(Longest Common Subsequence):给定两个字符串,找到它们的最长公共子序列,即在两个字符串中都存在的最长的子序列。最长公共子序列问题可以用于比较文本相似性、基因序列比对等领域。
- 最长回文子串(Longest Palindromic Substring):给定一个字符串,找到其中最长的回文子串,即正向和反向读都相同的最长子串。最长回文子串问题可以用于文本处理、字符串匹配等领域。
- 字符串压缩(String Compression):给定一个字符串,将连续出现的字符压缩成字符和出现次数的形式。例如,字符串"aaabbbccc"可以压缩成"a3b3c3"。字符串压缩问题可以用于数据压缩、编码等领域。
2.例题
1)
力扣https://leetcode.cn/problems/edit-distance/用dp[i][j[来表示word1的前i个字符要和word2的前j个字符完全匹配,所需要的操作次数。
当i=0时,表示word1为空,word2的前j个字符要与word1完全匹配,需要删除j个字符。
当j=0时,也是类似的情况。
接下来考虑更普遍的情况,如何计算dp[i][j]。
如果word1[i]==word2[j],这表明两个新字符相等,dp[i][j]=dp[i][j]
如果word1[i]!=word2[j],这表明两个新字符不相等,要使它们匹配,有三种方法:
- 修改word1或word2中其中一个字符,dp[i][j]=dp[i-1][j-1]
- 删除word1或word2中其中一个字符,dp[i][j]=dp[i-1][j]+1 或者 dp[i][j]=dp[i][j-1]+1
- 在word1或word2中增加一个字符,dp[i][j]=dp[i][j-1]+1 或者 dp[i][j]=dp[i-1][j]+1
这里通俗易懂地解释一下:
word1[i]!=word2[j],可以把word1[i]改成word1[j],这样就变成了相等的情况。
也可以删除掉word1[i],这样就需要word1[0...i-1]与word2[0...j]是相等的,所以就是dp[i-1][j]加一。
也可以在word1[i]后增加一个新字符word2[j],这样最后面的两个字符就相等了,但需要word1[0...i]与word2[0...j-1]是相等的,所以就是dp[i][j-1]加一。
class Solution {
public:
int minDistance(string word1, string word2) {
int m = word1.size(), n = word2.size();
vector<vector<int>> dp(m+1,vector<int>(n+1,max(m,n)+1));
for(int i=0;i<=n;i++){
dp[0][i] = i;
}
for(int i=1;i<=m;i++){
dp[i][0] = i;
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(word1[i-1]==word2[j-1]){
dp[i][j] = dp[i-1][j-1];
}else{
dp[i][j] = 1 + min(dp[i-1][j-1],min(dp[i][j-1], dp[i-1][j]));
}
}
}
return dp[m][n];
}
};
2)
力扣https://leetcode.cn/problems/2-keys-keyboard/n的最少操作次数,却决于它的因子的最少操作次数。
如果n可以整除j,那么n的最少操作次数是j的最少操作次数,加上复制的一次操作和粘贴的n/j-1次操作。
也就是:dp[n] = min(dp[j]+n/j, dp[n/j]+j)
因为j和n/j地位一样,都是n的因子,所以取二者中的最小值
class Solution {
public:
int minSteps(int n) {
vector<int> dp(n+1,INT_MAX);
dp[0] = 0;
dp[1] = 0;
for(int i=2;i<=n;i++){
for(int j=1;j*j<=i;j++){
if(i%j==0){
dp[i] = min(dp[i], dp[j]+i/j);
dp[i] = min(dp[i], dp[i/j]+j);
}
}
}
return dp[n];
}
};
3)
力扣https://leetcode.cn/problems/regular-expression-matching/这道题真的好无语,首先这题目出的就有歧义:
'*'
匹配零个或多个前面的那一个元素
看到这句话,我想当然地觉得是*可以为0到多个前一个元素。
看完题解才知道,*意味着前一个元素也可以为空,*不是独立存在的,c*才是一个整体,这个整体可以为空。
基于这个假设,我设想的很复杂的c****,也是不合法的输入。
搞清楚这一点后,就清晰起来了,s和p匹配无非三种情况:
- p是普通字母
- p是.
- p是*
dp[i][j]表示s[0...i-1]与p[0...j-1]的匹配情况
在前两种情况下都是正常判断是否匹配,'.'与所有字母匹配,所以:
dp[i][j] = dp[i-1][j-1] 如果s[i-1]与p[j-1]匹配成功 否则为false。
接下来考虑第三种情况,p为*
可以有两种处理方式,一是直接不匹配,把c*当作空,所以:
dp[i][j] = dp[i][j-2]
二是s[i-1]与p[j-2]匹配成功:
dp[i][j] = dp[i-1][j] 这表示p[j-1]可以与s前面的元素匹配
代码:
class Solution {
public:
bool isMatch(string s, string p) {
int m = s.size(), n = p.size();
auto isMatch = [&](int i, int j){
if(i==0) return false;
if(p[j-1]=='.') return true;
return s[i-1]==p[j-1];
};
vector<vector<bool>> dp(m+1,vector<bool>(n+1,false));
dp[0][0] = true;
for(int i=0;i<=m;i++){
for(int j=1;j<=n;j++){
if(p[j-1]!='*'){
if(isMatch(i,j)){
dp[i][j] = dp[i-1][j-1];
}
}else{
dp[i][j] = dp[i][j-2];
if(isMatch(i,j-1)){
dp[i][j] = dp[i][j] | dp[i-1][j];
}
}
}
}
return dp[m][n];
}
};