⭐算法入门⭐《动态规划 - 串匹配》困难02 —— LeetCode 72. 编辑距离

🙉饭不食,水不饮,题必须刷🙉

C语言免费动漫教程,和我一起打卡!
🌞《光天化日学C语言》🌞

LeetCode 太难?先看简单题!
🧡《C语言入门100例》🧡

数据结构难?不存在的!
🌳《画解数据结构》🌳

LeetCode 太简单?算法学起来!
🌌《夜深人静写算法》🌌

一、题目

1、题目描述

给定两个长度不超过 500 的单词 A A A B B B,请计算出将 A A A 转换成 B B B 所使用的最少操作数 。可以对一个单词进行如下三种操作:
  1)插入一个字符;
  2)删除一个字符;
  3)替换一个字符;

  样例输入: A = "intentiona"B = "executiona"
  样例输出: 5

2、基础框架

  • c++ 版本给出的基础框架代码如下:
class Solution {
public:
    int minDistance(string word1, string word2) {
    }
};
  • word1就是源串 A;
  • word2就是目标串 B;

3、原题链接

LeetCode 72. 编辑距离

二、解题报告

1、思路分析

  • f ( i , j ) f(i, j) f(i,j) 表示 源串前缀 a 0 a 1 . . . a i a_0a_1...a_{i} a0a1...ai 变成 目标串前缀 b 0 b 1 . . . b j b_0b_1...b_{j} b0b1...bj 的最小操作数。
  • I I I D D D R R R 分别为插入一个字符、删除一个字符、替换一个字符的操作数,本题中值均为 1。

1)插入

  • 假设 a 0 a 1 . . . a i a_0a_1...a_{i} a0a1...ai 变成 b 0 b 1 . . . b j − 1 b_0b_1...b_{j-1} b0b1...bj1 的最少操作数已经求出,等于 f ( i , j − 1 ) f(i,j-1) f(i,j1),则需要在 a [ i ] a[i] a[i] 的后面插入一个字符 b j b_j bj,那么最少操作数为:
  • f ( i , j − 1 ) + I f(i, j-1) + I f(i,j1)+I
  • 如图所示,源字符串为 “AGTA”,目标字符串为 “GATCGT” 的情况下,将源字符串变成 "“GATCG” 的最小消耗为 f ( i , j − 1 ) f(i,j-1) f(i,j1),那么只要在源字符串最后再插入一个 ‘T’,就可以把源字符串变成目标字符串 “GATCGT”;

2)删除

  • 假设 a 0 a 1 . . . a i − 1 a_0a_1...a_{i-1} a0a1...ai1 变成 b 0 b 1 . . . b j b_0b_1...b_{j} b0b1...bj 的最少操作数已经求出,等于 f ( i − 1 , j ) f(i-1,j) f(i1,j),则需要把 a i a_i ai 个删掉,那么产生的最少操作数为:
  • f ( i − 1 , j ) + D f(i-1,j) + D f(i1,j)+D
  • 如图所示,源字符串为 “AGTA”,目标字符串为 “GATCGT” 的情况下,将 “AGT” 变成目标字符串的最小消耗为 f ( i − 1 , j ) f(i-1,j) f(i1,j),那么只要把源字符串最后一个’A’删掉,就可以把源字符串变成目标字符串;

在这里插入图片描述

3)替换

  • 假设 a 0 a 1 . . . a i − 1 a_0a_1...a_{i-1} a0a1...ai1 变成 b 0 b 1 . . . b j − 1 b_0b_1...b_{j-1} b0b1...bj1 的最少消耗已经求出,等于 f ( i − 1 , j − 1 ) f(i-1,j-1) f(i1,j1),则将 a i a_i ai 替换成 b j b_j bj a 0 , a 1 , . . . , a i a_0,a_1,...,a_{i} a0,a1,...,ai 就可以变成 b 0 , b 1 , . . . b j b_0,b_1,...b_{j} b0,b1,...bj。替换时需要考虑 a i = b j a_i=b_j ai=bj a i ≠ b j a_i \neq b_j ai=bj 的情况,所以替换产生的最小操作数为:
  • f ( i − 1 , j − 1 ) + { 0 a i = b j R a i ≠ b j f(i-1,j-1) + \begin{cases} 0 & a_i=b_j \\ R & a_i \neq b_j\end{cases} f(i1,j1)+{0Rai=bjai=bj
  • 如图所示,源字符串为 “AGTA”,目标字符串为 “GATCGT” 的情况下,将 “AGT” 变成 “GATCGT” 的最小操作数为 f ( i − 1 , j − 1 ) f(i-1,j-1) f(i1,j1),那么只要将 源字符串 的最后一个字符 替换为 目标字符串 的最后一个字符 ,就可以把源字符串变成目标字符串;替换时根据 源字符串 和 目标字符串 原本是否相等来决定操作数;

4)边界处理

  • 边界情况主要考虑以下几种:

a. 空串变成目标串

  • f ( − 1 , j ) f(-1,j) f(1,j),总共需要插入 j + 1 j+1 j+1 个字符,所以 f ( − 1 , j ) = f ( − 1 , j − 1 ) + I f(-1,j) = f(-1,j-1) + I f(1,j)=f(1,j1)+I

b. 源字符串变成空串

  • f ( i , − 1 ) f(i,-1) f(i,1),总共需要删除 i + 1 i+1 i+1 个字符,所以 f ( i , − 1 ) = f ( i − 1 , − 1 ) + D f(i,-1) = f(i-1,-1) + D f(i,1)=f(i1,1)+D

c. 空串变成空串

  • f ( − 1 , − 1 ) = 0 f(-1,-1) = 0 f(1,1)=0

2、状态转移方程

  • 将上述所有状态进行一个整合,得到状态转移方程如下:
  • f ( i , j ) = { 0 i = − 1 , j = − 1 f ( i , j − 1 ) + I i = − 1 , j ≥ 0 f ( i − 1 , j ) + D i ≥ 0 , j = − 1 min ⁡ i ≥ 0 , j ≥ 0 { f ( i , j − 1 ) + I f ( i − 1 , j ) + D f ( i − 1 , j − 1 ) + R a i ≠ b j f(i,j) = \begin{cases}0 & i=-1,j=-1\\f(i,j-1)+I & i=-1,j\ge0\\ f(i-1,j) + D & i\ge0,j=-1 \\ \min_{i\ge0,j\ge0} \begin{cases} f(i,j-1) + I\\ f(i-1,j) + D\\ f(i-1,j-1) + R_{a_i \neq b_j}\end{cases}\end{cases} f(i,j)=0f(i,j1)+If(i1,j)+Dmini0,j0f(i,j1)+If(i1,j)+Df(i1,j1)+Rai=bji=1,j=1i=1,j0i0,j=1
  • 如果源串长度为 n n n,目标串长度为 m m m,则通过这个状态矩阵,最后计算得到 f ( n − 1 , m − 1 ) f(n-1,m-1) f(n1,m1) 就是该题所求 "源字符串 a 0 , a 1 , . . . , a n − 1 a_0,a_1,...,a_{n-1} a0,a1,...,an1,经过 插入、删除、替换 变成目标字符串 b 0 , b 1 , . . . b m − 1 b_0,b_1,...b_{m-1} b0,b1,...bm1" 的最少操作数了。
  • 有关编辑距离的更多内容,可以参考:夜深人静写算法(二十二)- 最小编辑距离

3、时间复杂度

  • 源串的长度为 n n n,目标串的长度为 m m m
  • 状态数: O ( n m ) O(nm) O(nm)
  • 状态转移: O ( 1 ) O(1) O(1)
  • 时间复杂度: O ( n m ) O(nm) O(nm)

4、代码详解

#define I 1
#define D 1
#define R 1
const int maxn = 505;
int dp[maxn][maxn];

class Solution {
    void setdp(int r, int c, int val) {
        dp[r+1][c+1] = val;                              // (1)
    }
    int getdp(int r, int c) {
        if(r == -1 && c == -1) {
            return 0;                                    // (2)
        }
        return dp[r+1][c+1];                             // (3)
    }
public:
    int minDistance(string word1, string word2) {
        int n = word1.size();
        int m = word2.size();
        memset(dp, 0x7f7f7f7f, sizeof(dp));              // (4)
        for(int i = 0; i < word1.size(); ++i) {
            setdp(i, -1, (i+1) * D);                     // (5)
        }
        for(int i = 0; i < word2.size(); ++i) {
            setdp(-1, i, (i+1) * I);                     // (6)
        }
        for(int i = 0; i < word1.size(); ++i) {
            for(int j = 0; j < word2.size(); ++j) {
                int ICost = getdp(i, j-1) + I;
                int DCost = getdp(i-1, j) + D;
                int RCost = getdp(i-1, j-1) + ( (word1[i] == word2[j]) ? 0 : R);
                int ans = min( min(ICost, DCost), RCost );   // (7)
                setdp(i, j, ans);
            }
        }
        return getdp(word1.size()-1, word2.size()-1);
    }
};
  • ( 1 ) (1) (1) 设置状态数组的值,偏移+1用来避免数组下标越界;
  • ( 2 ) (2) (2) 空串 变成 空串 的情况;
  • ( 3 ) (3) (3) 获取状态数组的值;
  • ( 4 ) (4) (4) 初始化将状态数组中的所有状态变成一个非常大的值;
  • ( 5 ) (5) (5) 初始状态:源串 变 空串;
  • ( 6 ) (6) (6) 初始状态:空串 变 目标串;
  • ( 7 ) (7) (7) 参考上文阐述的状态转移方程;

三、本题小知识

  动态规划问题需要用到数组来进行数据缓存,但是有时候会发现状态存在负数,这时候就需要加上一个偏移量,来放置数组下标越界,本文中的偏移量为1。


评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

英雄哪里出来

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

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

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

打赏作者

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

抵扣说明:

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

余额充值