蓝桥杯2019年第十届软件类国赛C/C++B组problemF最优包含(DP + 优化)

题目

在这里插入图片描述

思路分析

本题是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:m1] P P P [ j : n − 1 ] [j: n - 1] [j:n1]的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[i1][j1]
对于 s [ i ] ≠ p [ j ] s[i]≠p[j] s[i]=p[j],此时有两种选择:
\qquad 1.即刻修改字符
\qquad 2.本次暂不修改,留到之后看看能不能继续匹配上
对于本次修改字符,上一个状态是 ( i − 1 , j − 1 ) (i - 1, j - 1) (i1,j1)
所以 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 dp[i][j] = dp[i - 1][j - 1] + 1 dp[i][j]=dp[i1][j1]+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[i1][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[i1][j1]+1,dp[i1][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[i1][j1]min(dp[i1][j1]+1,dp[i1][j])s[i]==p[j]s[i]=p[j]
特别地,为了防止 i − 1 i - 1 i1越界,在定义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[i1][j1] d p [ i − 1 ] [ j ] dp[i -1][j] dp[i1][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:m1][0:n1]的情况

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Capzera

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值