【华为od刷题(C++)】HJ52 计算字符串的编辑距离

我的代码:

#include //用于输入输出
#include //包含一些常用算法,如 min 等
#include //用于动态数组(二维数组)
#include //用于处理字符串

using namespace std;

int main() {
    string str1, str2;//str1 和 str2 是输入的两个字符串
    while (cin >> str1 >> str2) {
        int n = str1.size();
        int m = str2.size();
        //n 和 m 分别存储 str1 和 str2 的长度

        vector> dp(n + 1, vector(m + 1, 0));
        //创建一个二维动态数组 dp 来存储计算中间结果

        /*dp[i][j] 表示:
        将 str1 的前 i 个字符转换为 str2 的前 j 个字符的最小编辑距离
        即将 str1[0..i-1] 转换为 str2[0..j-1] 的最小操作数*/

        /*这两个循环分别对 dp 数组的第一行和第一列进行初始化*/
        for (int i = 1; i <= n; ++i) {
            dp[i][0] = i;
        }
        /*dp[i][0] = i 表示:
        将 str1 的前 i 个字符转换为空串的操作次数为 i(即删除操作)*/

        for (int i = 1; i <= m; ++i) {
            dp[0][i] = i;
        }
        /*dp[0][i] = i 表示:
        将空串转换为 str2 的前 i 个字符的操作次数为 i(即插入操作)*/

        for (int i = 1; i <= n; ++i) {
            //i 从 1 到 n,表示遍历 str1 中的每个字符

            for (int j = 1; j <= m; ++j) {
                /*对于每个外循环的 i,内循环遍历 str2 的每个字符,
                表示要将 str1 的前 i 个字符转换为 str2 的前 j 个字符*/

                //双重循环遍历二维数组 dp

                int op1 = dp[i - 1][j - 1];
                //如果当前字符相等,不需要操作;否则,需要替换,操作数加 1

                int op2 = dp[i][j - 1] + 1;
                //表示插入操作(将 str2[j-1] 插入到 str1 中),操作数加 1

                int op3 = dp[i - 1][j] + 1;
                //表示删除操作(从 str1 中删除 str1[i-1]),操作数加 1


                if (str1[i - 1] != str2[j - 1]) {
                    ++op1;
                }
                /*如果 str1[i - 1] 和 str2[j - 1] 不同,
                意味着需要进行替换操作
                在这种情况下,op1 的值会增加 1,
                表示我们需要进行一个替换操作来使这两个字符相等*/

                dp[i][j] = min(min(op2, op3), op1);
                /*dp[i][j] 存储了
                将 str1[0..i-1] 转换为 str2[0..j-1] 的最小编辑距离,
                取三个操作数中的最小值*/
            }
        }
        /*在 编辑距离(Levenshtein distance)算法中,
        dp[i][j] 表示将 str1[0..i-1] 转换为 str2[0..j-1] 的最小操作数

        假设我们要将 str1[0..i-1] 转换成 str2[0..j-1],
        对于每个 dp[i][j],我们需要考虑以下三种情况,并选择代价最小的操作:

        替换操作:
        如果 str1[i-1] 与 str2[j-1] 相同,则无需替换,
        代价为 dp[i-1][j-1],
        即从 str1[0..i-2] 到 str2[0..j-2] 的最小操作数

        如果 str1[i-1] 与 str2[j-1] 不同,
        则代价为 dp[i-1][j-1] + 1,
        即从 str1[0..i-2] 到 str2[0..j-2] 的最小操作数,
        再加上替换操作的代价

        插入操作:
        在 str1 的当前位置插入 str2[j-1],代价为 dp[i][j-1] + 1,
        表示从 str1[0..i-1] 到 str2[0..j-2] 的最小操作数,
        再加上插入一个字符的代价

        删除操作:
        删除 str1[i-1],代价为 dp[i-1][j] + 1,
        表示从 str1[0..i-2] 到 str2[0..j-1] 的最小操作数,
        再加上删除一个字符的代价*/

        cout << dp[n][m] << endl;
        /*最终的编辑距离是 dp[n][m],
        表示将整个 str1 转换为整个 str2 的最小操作次数*/
    }
    return 0;
}

该代码实现了一个经典的 编辑距离(Levenshtein distance)算法,用来计算两个字符串之间的最小编辑操作次数;编辑操作包括插入、删除和替换;以下是代码的详细思路总结:

1. 定义问题

目标是计算将字符串 str1 转换为字符串 str2 所需要的最小操作次数,操作包括:

  • 插入:在 str1 中插入一个字符
  • 删除:从 str1 中删除一个字符
  • 替换:将 str1 中的某个字符替换为 str2 中的字符

2. 动态规划(DP)思想

使用一个二维动态规划数组 dp,其中 dp[i][j] 表示将 str1[0..i-1] 转换为 str2[0..j-1] 的最小操作数

  • 初始化
    • dp[i][0] = i:将 str1[0..i-1] 转换为空串需要 i 次删除操作
    • dp[0][j] = j:将空串转换为 str2[0..j-1] 需要 j 次插入操作

3. 状态转移

通过三种基本操作来递推计算 dp[i][j]

  • 替换操作:如果 str1[i-1] != str2[j-1],则需要进行替换,dp[i][j] = dp[i-1][j-1] + 1
  • 插入操作:将 str2[j-1] 插入到 str1 中,dp[i][j] = dp[i][j-1] + 1
  • 删除操作:将 str1[i-1] 从 str1 中删除,dp[i][j] = dp[i-1][j] + 1

对于每个 dp[i][j],取这三种操作的最小值:

dp[i][j] = min(min(dp[i-1][j-1] + (str1[i-1] != str2[j-1]), dp[i][j-1] + 1), dp[i-1][j] + 1);
  • dp[i-1][j-1] + (str1[i-1] != str2[j-1]):表示如果字符不同,则需要替换
  • dp[i][j-1] + 1:表示需要插入一个字符
  • dp[i-1][j] + 1:表示需要删除一个字符

4. 最终结果

最终,dp[n][m]nstr1 的长度,mstr2 的长度)会存储将整个 str1 转换为 str2 所需的最小操作次数

5. 代码工作流程

  • 先输入两个字符串 str1 和 str2
  • 初始化 dp 数组,设置边界条件
  • 通过双重循环填充 dp 数组,计算每一对子字符串的最小编辑距离
  • 最后输出 dp[n][m],即两个字符串的最小编辑距离

6. 代码细节

  • vector> dp(n + 1, vector(m + 1, 0)):创建一个 (n+1) x (m+1) 的二维动态数组,用于存储中间结果
  • 输入输出:使用 cin 读取输入,使用 cout 输出结果

代码关键部分:

dp[i][j] = min(min(dp[i-1][j-1] + (str1[i-1] != str2[j-1]), dp[i][j-1] + 1), dp[i-1][j] + 1);

7. 示例

输入:

kitten sitting

输出:

3

解释:将 "kitten" 转换为 "sitting" 需要三个操作:替换 ks,替换 ei,插入 g

8. 时间复杂度与空间复杂度

  • 时间复杂度O(n * m),其中 n 是 str1 的长度,m 是 str2 的长度;每个 dp[i][j] 需要常数时间进行计算
  • 空间复杂度O(n * m),需要一个 n x m 的二维数组来存储中间结果

总结:该算法使用动态规划求解字符串的编辑距离问题,能够高效地处理字符串之间的最小操作数问题,并且提供了清晰的状态转移方程

我们来计算从 str1 = "kitten"str2 = "sitting" 的最小编辑距离

初始化 dp 表

    ""  s  i  t  t  i  n  g
""   0  1  2  3  4  5  6  7
k    1
i    2
t    3
t    4
e    5
n    6
  • dp[0][0] = 0:两个空字符串的编辑距离为0
  • dp[i][0] = i:将 str1[0..i-1] 转换为空字符串,删除 i 个字符
  • dp[0][j] = j:将空字符串转换为 str2[0..j-1],插入 j 个字符

填充 dp 表

接下来,我们逐步填充 dp 表,使用递推公式来计算每个 dp[i][j]

  1. 比较 str1[0] = k 和 str2[0] = s,它们不相同,所以进行替换,dp[1][1] = min(dp[0][1] + 1, dp[1][0] + 1, dp[0][0] + 1) = min(2, 2, 1) = 1
  2. 比较 str1[0] = k 和 str2[1] = i,它们不相同,dp[1][2] = min(dp[0][2] + 1, dp[1][1] + 1, dp[0][1] + 1) = min(3, 2, 2) = 2
  3. 比较 str1[0] = k 和 str2[2] = t,它们不相同,dp[1][3] = min(dp[0][3] + 1, dp[1][2] + 1, dp[0][2] + 1) = min(4, 3, 3) = 3
  4. 比较 str1[0] = k 和 str2[3] = t,它们不相同,dp[1][4] = min(dp[0][4] + 1, dp[1][3] + 1, dp[0][3] + 1) = min(5, 4, 4) = 4
  5. 比较 str1[0] = k 和 str2[4] = i,它们不相同,dp[1][5] = min(dp[0][5] + 1, dp[1][4] + 1, dp[0][4] + 1) = min(6, 5, 5) = 5
  6. 比较 str1[0] = k 和 str2[5] = n,它们不相同,dp[1][6] = min(dp[0][6] + 1, dp[1][5] + 1, dp[0][5] + 1) = min(7, 6, 6) = 6
  7. 比较 str1[0] = k 和 str2[6] = g,它们不相同,dp[1][7] = min(dp[0][7] + 1, dp[1][6] + 1, dp[0][6] + 1) = min(8, 7, 7) = 7

依此类推,我们继续填充整个 dp 表;最终的 dp 表如下所示:

    ""  s  i  t  t  i  n  g
""   0  1  2  3  4  5  6  7
k    1  1  2  3  4  5  6  7
i    2  2  1  2  3  4  5  6
t    3  3  2  1  2  3  4  5
t    4  4  3  2  1  2  3  4
e    5  5  4  3  2  2  3  4
n    6  6  5  4  3  3  2  3

结果

最终,dp[6][7] = 3,表示将 "kitten" 转换为 "sitting" 所需的最小编辑距离是 3

解释最小编辑距离的操作:

  1. 替换 k 为 s,使得 kitten 变为 sitten
  2. 替换 e为 i,使得 sitten 变为 sittin
  3. 插入 g,使得 sittin 变为 sitting

因此,最小的编辑距离是 3

你可能感兴趣的:(华为od,c++,开发语言)