当前位置: 首页 > news >正文

每日一题——编辑距离

编辑距离

    • 参考资料
    • 题目描述
      • 示例
    • 解题思路
      • 动态规划(DP)方法
    • 代码实现
    • 复杂度分析
    • 示例详解
      • 示例1:`"nowcoder"` → `"new"`
      • 示例2:`"intention"` → `"execution"`
    • 总结与心得

参考资料

建议先参考下面一个视频和文档,然后再思考问题,不然很容易会被吓到。
——

https://programmercarl.com/0072.%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE
在这里插入图片描述

题目描述

给定两个字符串 str1str2,计算将 str1 转换为 str2 所需的最小操作次数。每次操作可以执行以下三种操作之一:

  • 插入一个字符
  • 删除一个字符
  • 修改一个字符

示例

  • 示例1:
    • 输入:"nowcoder", "new"
    • 输出:6
    • 解释:修改 ‘o’ 为 ‘e’,删除后续5个字符
  • 示例2:
    • 输入:"intention", "execution"
    • 输出:5
    • 解释:需要修改前5个字符

解题思路

动态规划(DP)方法

  1. 状态定义

    • dp[i][j] 表示将 str1 的前 i 个字符转换为 str2 的前 j 个字符所需的最小操作次数
  2. 状态转移方程

    • str1[i-1] == str2[j-1] 时:dp[i][j] = dp[i-1][j-1]
    • 否则,取三种操作的最小值加1:
      • 修改操作:dp[i-1][j-1] + 1
      • 插入操作:dp[i][j-1] + 1
      • 删除操作:dp[i-1][j] + 1
  3. 初始化

    • dp[i][0] = i:表示删除操作
    • dp[0][j] = j:表示插入操作

代码实现

#include <string.h>  // 引入字符串处理函数库,用于调用strlen等函数

// 辅助函数:计算三个整数中的最小值
int min(int a, int b, int c) {
    // 使用三元运算符实现最小值计算
    // 首先比较a和b,取较小值,再与c比较,最终返回最小值
    return a < b ? (a < c ? a : c) : (b < c ? b : c);
}

// 主函数:计算两个字符串之间的编辑距离
int editDistance(char* str1, char* str2) {
    // 获取两个字符串的长度
    int len1 = strlen(str1);  // str1的长度
    int len2 = strlen(str2);  // str2的长度

    // 处理空字符串情况
    // 如果str1为空,将str2的所有字符插入到str1中,需要len2次操作
    if (len1 == 0) return len2;
    // 如果str2为空,将str1的所有字符删除,需要len1次操作
    if (len2 == 0) return len1;

    // 创建动态规划表dp,大小为(len1+1)×(len2+1)
    // dp[i][j]表示str1的前i个字符转换为str2的前j个字符所需的最小操作数
    int dp[len1 + 1][len2 + 1];

    // 初始化动态规划表的第一行和第一列
    // dp[i][0]:将str1的前i个字符转换为空字符串,需要i次删除操作
    for (int i = 0; i <= len1; i++) dp[i][0] = i;
    // dp[0][j]:将空字符串转换为str2的前j个字符,需要j次插入操作
    for (int j = 0; j <= len2; j++) dp[0][j] = j;

    // 动态规划过程:填充dp表
    // 遍历str1和str2的每个字符
    for (int i = 1; i <= len1; i++) {
        for (int j = 1; j <= len2; j++) {
            // 如果当前字符相同,不需要额外操作,直接继承左上角的值
            if (str1[i - 1] == str2[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                // 如果当前字符不同,选择三种操作的最小值 + 1
                // dp[i - 1][j - 1]:替换操作
                // dp[i][j - 1]:插入操作
                // dp[i - 1][j]:删除操作
                dp[i][j] = min(dp[i - 1][j - 1], dp[i][j - 1], dp[i - 1][j]) + 1;
            }
        }
    }

    // 返回最终的编辑距离,即dp表右下角的值
    return dp[len1][len2];
}

复杂度分析

  • 时间复杂度:O(mn),其中m和n分别是两个字符串的长度
  • 空间复杂度:O(mn),需要一个二维数组存储动态规划的状态

示例详解

示例1:"nowcoder""new"

  1. 对于第一个位置,两个字符串都是’n’,不需要操作
  2. 第二个位置,需要将’o’修改为’e’,操作数+1
  3. 接下来需要删除"wcoder"这5个字符,每个字符删除操作数+1
  4. 最终需要6次操作

示例2:"intention""execution"

  1. 两个字符串后缀"tion"相同,不需要操作
  2. 需要修改前5个字符,每个字符修改操作数+1
  3. 最终需要5次操作

总结与心得

这道题虽然看起来操作较多(增删改),但实际难度适中,比起IP地址字符串转换的题目要简单一些。关键在于理解动态规划的状态设计:

  1. 一开始可能会想直接用m×n的dp数组(去掉第一行第一列),但这样是不行的。因为如果两个字符串完全相同,结果应该是0,所以需要包含这种情况。

  2. 初始化很重要:第一行和第一列分别表示纯粹的删除和插入操作,这为后续的状态转移奠定了基础。

  3. 状态转移方程的设计体现了问题的本质:当字符相同时不需要操作,当字符不同时取三种操作的最小值。

相关文章:

  • DeepSeek 助力 Vue 开发:打造丝滑的瀑布流布局(Masonry Layout)
  • 【读取filePath这个文件中的内容,并打印出来】+【if else 的优化】
  • ubuntu 磁盘恢复
  • 假期学习总结(25.2.19)
  • Ubuntu USB耳机找不到设备解决
  • 力士乐伺服电机MSK系列型号
  • 初识Redis
  • DeepSeek 解析
  • 深入理解 lua_KFunction 和 lua_CFunction
  • 网络安全java练习平台 js网络安全
  • 大数据组件(四)快速入门实时数据湖存储系统Apache Paimon(2)
  • 【Linux】【网络】Libevent整个的使用流程总结(与接口函数结合)
  • 关于SOC与CPU的那些事
  • 网络安全钓鱼邮件测试 网络安全 钓鱼
  • React之旅-03 路由
  • Qt学习(五)自定义对话框,多窗口开发---添加设计师类, MDI多窗口开发
  • ARM TCM(itcm和dtcm)
  • 如何用ollama快速布署deepseek-r1大模型
  • 学习数据结构(11)二叉树(堆)下
  • tauri-plugin-http插件暂时不支持流传输Streaming,所以大模型的流传输就难了,所以还是用js的请求吧
  • 美联储官员:美国经济增速可能放缓,现行关税政策仍将导致物价上涨
  • 广西壮族自治区党委常委会:坚决拥护党中央对蓝天立进行审查调查的决定
  • 巴菲特最新调仓:一季度大幅抛售银行股,再现保密仓位
  • 丹麦外交大臣拉斯穆森将访华
  • 台行政机构网站删除“汉人”改为“其余人口”,国台办回应
  • 王毅谈中拉命运共同体建设“五大工程”及落实举措