题目
思路分析
本题是LeetCode 72.编辑距离的弱化版本
在编辑距离中,修改 操作有:
1.添加字符
2.替换字符
3.删除字符
本题中所涉及到的修改操作仅限于 2.替换字符,解法为 动态规划
设DP数组
我们将待匹配的字符串定义为文本串S,匹配字符串定义为模式串P(后文用S,P替代)
对于字符串子序列类的问题,我们在设DP数组的时候有两种设法
1.设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示
S
S
S中前
i
i
i个字符,
P
P
P中前
j
j
j个字符的xxxxx
2.设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示
S
S
S中
[
i
:
m
−
1
]
[i: m - 1]
[i:m−1],
P
P
P中
[
j
:
n
−
1
]
[j: n - 1]
[j:n−1]的xxxxx
对于本题来说,两种方法均可解,读者可以选择自己偏好的解法,本题中的主要思路均针对第一种设法。
设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示 S S S中前 i i i个字符, P P P中前 j j j个字符的最小修改次数
确定状态边界及答案
由于
i
i
i,
j
j
j分别表示两字符串的前缀字符串答案,所以对于状态
(
i
,
j
)
(i,j)
(i,j)的边界为
(
0
,
0
)
(0,0)
(0,0)
对于
S
S
S中取前
i
i
i个字符,
P
P
P中取前
0
0
0个字符时,最小修改次数当然是0
d
p
[
i
]
[
0
]
=
0
dp[i][0] = 0
dp[i][0]=0
答案为:
d
p
[
m
]
[
n
]
dp[m][n]
dp[m][n]
边界是
(
i
,
0
)
(i,0)
(i,0)答案是
(
m
,
n
)
(m, n)
(m,n)所以该题中的循环顺序为正序循环
转移方程的推导
对于
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j],通过分析得知,会影响到本题中状态变化的情况有两种:
s
[
i
]
=
p
[
j
]
s
[
i
]
≠
p
[
j
]
s[i] = p[j]\\ s[i] ≠ p[j]
s[i]=p[j]s[i]=p[j]
对于
s
[
i
]
=
=
p
[
j
]
s[i] == p[j]
s[i]==p[j],说明此时并不需要修改,那么有:
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
−
1
]
dp[i][j] = dp[i - 1][j - 1]
dp[i][j]=dp[i−1][j−1]
对于
s
[
i
]
≠
p
[
j
]
s[i]≠p[j]
s[i]=p[j],此时有两种选择:
\qquad
1.即刻修改字符
\qquad
2.本次暂不修改,留到之后看看能不能继续匹配上
对于本次修改字符,上一个状态是
(
i
−
1
,
j
−
1
)
(i - 1, j - 1)
(i−1,j−1)
所以
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
−
1
]
+
1
dp[i][j] = dp[i - 1][j - 1] + 1
dp[i][j]=dp[i−1][j−1]+1
对于本次不修改字符,
p
[
j
]
p[j]
p[j]将留到之后再匹配,所以答案先保留不计算
s
[
i
]
s[i]
s[i]的答案
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
dp[i][j] = dp[i - 1][j]
dp[i][j]=dp[i−1][j]
那么有:
d
p
[
i
]
[
j
]
=
m
i
n
(
d
p
[
i
−
1
]
[
j
−
1
]
+
1
,
d
p
[
i
−
1
]
[
j
]
)
dp[i][j] = min(dp[i - 1][j - 1] + 1, dp[i - 1][j])
dp[i][j]=min(dp[i−1][j−1]+1,dp[i−1][j])
综上所述,可以写出如下转移方程:
d
p
[
i
]
[
j
]
=
{
d
p
[
i
−
1
]
[
j
−
1
]
s
[
i
]
=
=
p
[
j
]
m
i
n
(
d
p
[
i
−
1
]
[
j
−
1
]
+
1
,
d
p
[
i
−
1
]
[
j
]
)
s
[
i
]
≠
p
[
j
]
dp[i][j] = \begin{cases} dp[i - 1][j - 1] & s[i] == p[j] \\ min(dp[i - 1][j - 1] + 1, dp[i - 1][j]) & s[i]≠p[j] \\ \end{cases}
dp[i][j]={dp[i−1][j−1]min(dp[i−1][j−1]+1,dp[i−1][j])s[i]==p[j]s[i]=p[j]
特别地,为了防止
i
−
1
i - 1
i−1越界,在定义DP数组的时候,可以往右对齐一位
代码片:
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
int main() {
string s, p;
cin >> s >> p;
int m = s.size(), n = p.size();
vector<vector<int>> dp(m + 1, vector<int> (n + 1, inf));
dp[0][0] = 0;
for (int i = 1; i <= m; ++i) {
dp[i][0] = 0;
for (int j = 1; j <= n; ++j) {
if (s[i - 1] == p[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]);
}
}
cout << dp[m][n] << endl;
return 0;
}
时间复杂度:
O
(
m
n
)
O(mn)
O(mn),其中
m
m
m为
S
S
S串长度,
n
n
n为
P
P
P串的长度
空间复杂度:
O
(
m
n
)
O(mn)
O(mn),其中
m
m
m为
S
S
S串长度,
n
n
n为
P
P
P串的长度
空间优化
不难发现 d p [ i ] [ j ] dp[i][j] dp[i][j]仅和 d p [ i − 1 ] [ j − 1 ] dp[i - 1][j - 1] dp[i−1][j−1]与 d p [ i − 1 ] [ j ] dp[i -1][j] dp[i−1][j]有关,可以用两个一维数组做空间优化
#include <bits/stdc++.h>
using namespace std;
int main() {
string s, p;
cin >> s >> p;
int m = s.size(), n = p.size();
vector<int> dp(n + 1), dp1(n + 1, 0x3f3f3f3f);
dp1[0] = 0;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
dp[j] = dp1[j - 1];//dp[i][j] = dp[i - 1][j - 1]
if (s[i - 1] != p[j - 1]) {
//dp[i][j] = min(dp[i - 1][j - 1] + 1, dp[i - 1][j])
dp[j] = min(dp[j] + 1, dp1[j]);
}
}
dp1 = dp;
}
cout << dp[n] << endl;
return 0;
}
时间复杂度:
O
(
m
n
)
O(mn)
O(mn),其中
m
m
m为
S
S
S串长度,
n
n
n为
P
P
P串的长度
空间复杂度:
O
(
n
)
O(n)
O(n),其中
n
n
n为
P
P
P串的长度
另外一种DP数组的设法
其实在本题中就是相当于把DP倒过来做,最终答案返回 [ 0 : m − 1 ] [ 0 : n − 1 ] [0 : m -1][0 : n - 1] [0:m−1][0:n−1]的情况
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
int main() {
string s, p;
cin >> s >> p;
int m = s.size(), n = p.size();
vector<vector<int>> dp(m + 1, vector<int> (n + 1, inf));
dp[m][n] = 0;
for (int i = m - 1; i >= 0; --i) {
dp[i][n] = 0;
for (int j = n - 1; j >= 0; --j) {
if (s[i] == p[j])
dp[i][j] = dp[i + 1][j + 1];
else
dp[i][j] = min(dp[i + 1][j + 1] + 1, dp[i + 1][j]);
}
}
cout << dp[0][0] << endl;
return 0;
}