最少字符运算数

最少字符运算数

题目描述

给定两个字符串A和B以及下列三种字符运算:

(1)删除一个字符;

(2)插入一个字符;

(3)将一个字符改写为另一个字符。

问题是需要求出将字符串A转换为字符串B,所需要的最少的字符运算数。请设计一个有效算法,对任给定的2个字符串A和B,输出将字符串A转换为字符串B所需的最少字符运算数。

(来源:张德富,《算法设计与分析》 P93-P94:6.19)

题目分析

本题与求两个字符串的最长公共子序列类似,具体参考力扣1143。通过分析可知该问题存在重叠子问题,因此我们考虑动态规划算法。

以下提供两种思路:

(1)根据字符运算规则直接求递推式,通过循环嵌套直接得出结果。

(2)将字符串A转换为字符串B,只需要将A中与B不同的部分改为与B相同即可。

方法(一):直接求递推式

算法分析

(1)当A或B为空串时,dp[i,j]等于i或者j。

(2)i>0并且j>0时,对于dp[i,j],考虑四种操作情况:

①A子串删除A[j],有A[j-1]=B[i]:dp[i,j]=dp[i,j-1]+1;

②A子串插入A[j],有A[j]=B[i]:dp[i,j]=dp[i-1,j]+1;

③A子串修改A[j],使A[j]=B[i]:dp[i,j]=dp[i-1,j-1]+c,c=1;

④A[j]等于B[j],无需操作:dp[i,j]=dp[i-1,j-1]+c,c=0;

于是我们就得到了递推式:

			**dp[i, j] = min{dp[i, j-1]+1, dp[i-1, j]+1, dp[i-1, j-1]+c}**

其中c视情况而定。以下是该算法代码:

解决方案
#include<iostream>
#include<vector>
using namespace std;
int main()
{
    string a, b;
    cin >> a >> b;
    int lena = a.size(), lenb = b.size();
    vector< vector<int> >dp(lenb + 1, vector<int>(lena + 1, 0));
    for (int i = 0; i <= lenb; i++)
        dp[i][0] = i;
    for (int j = 0; j <= lena; j++)
        dp[0][j] = j;
    for (int i = 1; i <= lenb; i++)
    {
        for (int j = 1; j <= lena; j++)
        {
            dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);		//第(1)(2)种情况
            int t;
            if (b[i] == a[j])
                t = 0;
            else
                t = 1;
            dp[i][j] = min(dp[i][j], dp[i - 1][j - 1] + t);			//第(3)(4)种情况
        }
    }
    cout << "The minimum number of operations is " << dp[lenb][lena];
    return 0;
}

输入样例:

abbbcdddefshsdf
azzcdffefssdfsd

输出样例:

The minimum number of operations is 8

方法(二):改异为同

算法分析

先求出最大相同的部分,剩下的就是不同的部分,前者即为A与B的最长公共子序列(LCS)。用动态规划算法求出LCS并记录LCS中每一个字母在A和B中的位置,然后找到不同的部分。假设LCS共有n个字母,则A和B去掉LCS后都剩下n+1个部分(不含去掉的n个字母,有些部分可以是空串,相当于一条绳子切了n刀)。此时A和B中对应的部分不含公共子序列,假设两个部分的长度分别为N和M,则最少的运算次数肯定是max{N,M}。对每部分计算并求和即可求得最少运算次数。

程序中还添加了输出部分,用于显示A和B中的2n+1个部分,这样方便读者了解运算的过程。代码如下:

解决方案
#include <iostream>
#include <vector>
using namespace std;
struct LCS
{
    int length;             //公共子串长度
    vector<int>cha, chb;    //公共子串的字母在两个字符串的位置
};
int main()
{
    string a, b;
    cin >> a >> b;
    int lena = a.size(), lenb = b.size();
    vector< vector<LCS> >dp(lenb + 1, vector<LCS>(lena + 1));
    for (int i = 1; i <= lenb; i++)
    {
        for (int j = 1; j <= lena; j++)
        {
            if (b[i - 1] == a[j - 1])
            {
                dp[i][j].length = dp[i - 1][j - 1].length + 1;
                dp[i][j].chb = dp[i - 1][j - 1].chb;
                dp[i][j].chb.push_back(i - 1);
                dp[i][j].cha = dp[i - 1][j - 1].cha;
                dp[i][j].cha.push_back(j - 1);
            }
            else if (dp[i - 1][j].length > dp[i][j - 1].length)
            {
                dp[i][j].length = dp[i - 1][j].length;
                dp[i][j].chb = dp[i - 1][j].chb;
                dp[i][j].cha = dp[i - 1][j].cha;
            }
            else
            {
                dp[i][j].length = dp[i][j - 1].length;
                dp[i][j].chb = dp[i][j - 1].chb;
                dp[i][j].cha = dp[i][j - 1].cha;
            }
        }
    }
    LCS result = dp[lenb][lena];
    cout << "The length of LCS is " << result.length << endl;
    int len = result.cha.size();
    int transfer_time = 0;              //记录字符运算次数
    if ((result.cha[0]) || (result.chb[0]))
    {
        transfer_time += max(result.cha[0], result.chb[0]);
        cout << a.substr(0, result.cha[0]) << "  -->  " << b.substr(0, result.chb[0]) << endl;
    }
    for (int i = 0; i < len - 1; i++)
    {
        cout << a[result.cha[i]] << "  =  " << b[result.chb[i]] << endl;
        if ((result.cha[i + 1] - result.cha[i] - 1) || (result.chb[i + 1] - result.chb[i] - 1))
        {
            transfer_time += max(result.cha[i + 1] - result.cha[i] - 1, result.chb[i + 1] - result.chb[i] - 1);
            cout << a.substr(result.cha[i] + 1, result.cha[i + 1] - result.cha[i] - 1) << "  -->  " << b.substr(result.chb[i] + 1, result.chb[i + 1] - result.chb[i] - 1) << endl;
        }
    }
    cout << a[result.cha[len - 1]] << "  =  " << b[result.chb[len - 1]] << endl;
    if ((lena - result.cha[len - 1] - 1) || (lenb - result.chb[len - 1] - 1))
    {
        transfer_time += max(lena - result.cha[len - 1] - 1, lenb - result.chb[len - 1] - 1);
        cout << a.substr(result.cha[len - 1], lena - result.cha[len - 1] - 1) << "  -->  " << b.substr(result.chb[len - 1] + 1, lenb - result.chb[len - 1] - 1) << endl;
    }
    cout << "The minimum number of operations is " << transfer_time << endl;
    return 0;
}

输入样例:

abbbcdddefshsdf
azzcdffefssdfsd

输出样例:

The length of LCS is 9
a  =  a
bbb  -->  zz
c  =  c
d  =  d
dd  -->  ff
e  =  e
f  =  f
s  =  s
h  -->
s  =  s
d  =  d
f  =  f
  -->  sd
The minimum number of operations is 8

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

泠然鸢鸣

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

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

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

打赏作者

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

抵扣说明:

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

余额充值