06字符串编辑问题
# 06 字符串编辑问题
# 72 编辑距离 (opens new window)
给定两个字符串,已知你可以删除、替换和插入任意字符串的任意字符,求最少编辑几步可以将两个字符串变成相同。
输入是两个字符串,输出是一个整数,表示最少的步骤。
输入:word1 = "horse", word2 = "ros" 输出:3 解释: horse -> rorse (将 'h' 替换为 'r') rorse -> rose (删除 'r') rose -> ros (删除 'e')
解析:
本题与最长公共子序列类似,两个字符串的对比所以需要使用一个二维数组来设置状态 dp[i][j]
设置状态:dp[i][j]
表示将第一个字符串到位置 i 为止,和第二个字符串到位置 j 为止,最多需要几步编辑。
状态转移方程:当第 i 位和第 j 位对应的字符相同时,dp[i][j]
等于 dp[i-1][j-1]
;当二者对应的字符不同时,修改的消耗是 dp[i-1][j-1]+1
,插入 i 位置/删除 j 位置的消耗是 dp[i][j-1] + 1
,插入 j 位置/删除 i 位置的消耗是 dp[i-1][j] + 1
,所以在不同的情况下状态转移方程dp[i][j] = min(dp[i-1][j-1],dp[i-1][j],dp[i][j-1])
。
边界情况:word1为空串时,需要插入字符与word2一致,即dp[0][j] = j
;同理,word2为空串是dp[i][0] = i
class Solution {
public:
int minDistance(string word1, string word2) {
int m = word1.length(), n = word2.length();
vector<vector<int>> dp(m+1,vector<int>(n+1));
// 边界条件 word2为空串
for(int i=0;i<=m;++i){
dp[i][0] = i;
}
// 边界条件 word1为空串
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(word1[i-1] == word2[j-1]){
dp[i][j] = dp[i-1][j-1];
}else{
dp[i][j] = min(dp[i-1][j-1],min(dp[i-1][j],dp[i][j-1])) + 1;
}
}
}
return dp[m][n];
}
};
# 650 只有两个键的键盘 (opens new window)
给定一个字母 A,已知你可以每次选择复制全部字符,或者粘贴之前复制的字符,求最少需要几次操作可以把字符串延展到指定长度。
输入是一个正整数,代表指定长度;输出是一个整数,表示最少操作次数。
输入:3 输出:3 解释: 最初, 只有一个字符 'A'。 第 1 步, 使用 Copy All 操作。 第 2 步, 使用 Paste 操作来获得 'AA'。 第 3 步, 使用 Paste 操作来获得 'AAA'。
解析:
不同于以往通过加减实现的动态规划,这里需要乘除法来计算位置,因为粘贴操作是倍数增加的。
设置状态:使用一个一维数组 dp[i]
,其中位置 i 表示延展到长度 i 的最少操作次数。
状态转移方程:对于每个位置 j,如果 j 可以被 i 整除,那么长度 i 就可以由长度 j 操作得到,其操作次数等价于把一个长度为 1 的 A 延展到长度为 i/j。例如3由1操作得到A A A
,6由2操作得到AA AA AA
因此可以得到递推公式 dp[i] = dp[j] + dp[i/j]
。
class Solution {
public:
int minSteps(int n) {
vector<int> dp(n+1);
for(int i=2;i<=n;++i){
dp[i] = i;
for(int j=2;j<=i;++j){
if(i%j==0){
dp[i] = dp[j] + dp[i/j];
break;
}
}
}
return dp[n];
}
};
# 10 正则表达式匹配 (opens new window)
给定一个字符串和一个正则表达式(regular expression, regex),求该字符串是否可以被匹配,其中'.'
匹配任意单个字符;'*'
匹配零个或多个前面的那一个元素。
输入是一个待匹配字符串和一个用字符串表示的正则表达式,输出是一个布尔值,表示是否可以匹配成功。
输入:s = "aab" p = "cab" 输出:true 解释:因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。
解析:
两个字符串进行匹配,且字符具有特殊含义,本题动态规划的特点在于多种情况下的不同状态转移方程。
设置状态:使用一个布尔类型的二维数组 dp,其中 dp[i][j]
表示以 i 截止的字符串是否可以被以 j 截止的正则表达式匹配。
状态转移方程:根据正则表达式的不同情况,即字符、星号,点号,分情况讨论来更新 dp 数组
相等情况:即
s[i-1] == p[j-1] || p[j-1] == '.'
的情况,这种情况下dp[i][j] == dp[i-1][j-1]
,即与前 i-1 个子串匹配情况一致不相等情况:
s[i-1] != p[j-1] && p[j-1] != '*'
,直接不匹配情况dp[i][j] = false
p[j-1] == '*'
:如果匹配0次则dp[i][j-2] == true
则dp[i][j] = true
;如果匹配多次则需要验证'*'
前一个字符相等情况即s[i-1] == p[j-1] || p[j-1] == '.'
,相等则dp[i][j] = dp[i-1][j]
,否则不匹配
初始情况:空串匹配非空正则表达式,dp[0][0]=true
,当p[j-1]=='*'
是可以匹配0次匹配空串即dp[0][j] =dp[0][j-2]
;非空串匹配空正则表达式,dp[0][0]=true
,其他情况都无法成功匹配。
class Solution {
public:
bool isMatch(string s, string p) {
int m = s.size(), n = p.size();
vector<vector<bool>> dp(m+1,vector<bool>(n+1,false));
dp[0][0] = true;
for(int j=1;j<=n;++j){
if(p[j-1] == '*'){
dp[0][j] = dp[0][j-2];
}
}
for(int i=1;i<=m;++i){
auto chs = s[i-1];
for(int j=1;j<=n;++j){
auto chp = p[j-1];
if(chs == chp || chp == '.'){
dp[i][j] = dp[i-1][j-1];
}else if(chp == '*'){
if(j>1){
if(dp[i][j-2]){
dp[i][j] = true;
}else{
auto prechp = p[j-2];
if(prechp==chs || prechp =='.'){
dp[i][j] = dp[i-1][j];
}
}
}
}
}
}
return dp[m][n];
}
};