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

【LeetCode 热题 100】72. 编辑距离——(解法一)记忆化搜索

Problem: 72. 编辑距离

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度:O(m * n)
    • 空间复杂度:O(m * n)

整体思路

这段代码旨在解决经典的 “编辑距离” (Edit Distance) 问题,也称为 莱文斯坦距离 (Levenshtein Distance)。问题要求计算将一个字符串 word1 转换为另一个字符串 word2 所需的最少操作次数。允许的操作有三种:插入一个字符、删除一个字符、替换一个字符。

该算法采用的是一种 自顶向下(Top-Down)的动态规划 方法,即 记忆化搜索 (Memoization)。它将两个长字符串的编辑距离问题,分解为它们各种前缀之间的编辑距离子问题,并通过递归解决,同时使用一个二维数组来缓存已解决的子问题的答案,以避免重复计算。

算法的核心逻辑如下:

  1. 问题分解与递归关系

    • 算法的核心思想是比较两个字符串的末尾字符。
    • 我们定义一个函数 dfs(i, j),其含义是 “word1 的前 i+1 个字符(word1[0...i])与 word2 的前 j+1 个字符(word2[0...j])之间的最小编辑距离”。
    • 对于 dfs(i, j),有两种情况:
      a. 末尾字符相同 (w1[i] == w2[j]):如果两个字符串的最后一个字符相同,那么我们不需要对这两个字符进行任何操作。问题直接简化为计算它们各自去掉末尾字符后的编辑距离,即 dfs(i-1, j-1)
      b. 末尾字符不同 (w1[i] != w2[j]):如果最后一个字符不同,我们必须执行一次操作才能使它们匹配。有三种选择,我们取其中成本最小的:
      • 替换 (Replace):将 w1[i] 替换为 w2[j]。操作后末尾字符匹配,问题变为计算 word1[0...i-1]word2[0...j-1] 的距离。成本是 1 + dfs(i-1, j-1)
      • 删除 (Delete):删除 w1[i]。问题变为计算 word1[0...i-1]word2[0...j] 的距离。成本是 1 + dfs(i-1, j)
      • 插入 (Insert):在 w1 末尾插入 w2[j]。问题变为计算 w1[0...i]w2[0...j-1] 的距离。成本是 1 + dfs(i, j-1)
        因此,dfs(i, j) = 1 + min(dfs(i-1, j-1), dfs(i-1, j), dfs(i, j-1))
  2. 记忆化 (Memoization)

    • 纯粹的递归会导致子问题被大量重复计算,效率极低。
    • 算法使用二维 memo 数组存储 dfs(i, j) 的计算结果。在函数开头检查 memo,如果已计算,直接返回结果。
  3. 基础情况 (Base Cases)

    • if (i < 0): word1 为空串。要将其转换成 word2[0...j],需要 j+1 次插入操作。
    • if (j < 0): word2 为空串。要将 word1[0...i] 转换成空串,需要 i+1 次删除操作。

完整代码

import java.util.Arrays;class Solution {private char[] w1;private char[] w2;private int[][] memo;/*** 计算将 word1 转换成 word2 所使用的最少操作数。* @param word1 第一个单词* @param word2 第二个单词* @return 最小编辑距离*/public int minDistance(String word1, String word2) {w1 = word1.toCharArray();w2 = word2.toCharArray();int m = w1.length;int n = w2.length;// memo: 记忆化数组。memo[i][j] 存储 dfs(i, j) 的结果。memo = new int[m][n];// 初始化 memo 数组为 -1,表示所有子问题都尚未计算。for (int[] row : memo) {Arrays.fill(row, -1);}// 从两个字符串的末尾开始,自顶向下进行递归求解。return dfs(m - 1, n - 1);}/*** 记忆化搜索函数。* @param i      word1 的当前末尾字符索引* @param j      word2 的当前末尾字符索引* @return word1[0...i] 和 word2[0...j] 的最小编辑距离*/private int dfs(int i, int j) {// 基础情况:如果 word1 的前缀为空串if (i < 0) {// 需要 j+1 次插入操作才能变成 word2 的前缀return j + 1;}// 基础情况:如果 word2 的前缀为空串if (j < 0) {// 需要 i+1 次删除操作才能变成空串return i + 1;}// 记忆化检查:如果该子问题已计算过,直接返回。if (memo[i][j] != -1) {return memo[i][j];}// 状态转移:// 如果当前比较的两个字符相同if (w1[i] == w2[j]) {// 无需操作,距离等于它们各自去掉末尾字符后的距离。return memo[i][j] = dfs(i - 1, j - 1);}// 如果字符不同,必须执行一次操作。// 在 插入、删除、替换 三种操作中选择成本最小的一种。// dfs(i, j-1):   对应插入操作// dfs(i-1, j):   对应删除操作// dfs(i-1, j-1): 对应替换操作return memo[i][j] = Math.min(Math.min(dfs(i, j - 1), dfs(i - 1, j)), dfs(i - 1, j - 1)) + 1;}
}

时空复杂度

时间复杂度:O(m * n)

  • mword1 的长度,nword2 的长度。
  1. 状态数量:由于记忆化的存在,每个状态 (i, j) 只会被实际计算一次。
    • i 的范围是从 0m-1
    • j 的范围是从 0n-1
    • 因此,总的状态数量是 m * n
  2. 每个状态的计算时间:在 dfs 函数内部,除了递归调用,其他的操作都是 O(1) 的。

综合分析
总的时间复杂度等于(状态数量)×(每个状态的计算时间),即 O(m * n)。记忆化将算法从指数级 O(3^(m+n)) 优化到了多项式级。

空间复杂度:O(m * n)

  1. 记忆化数组:算法创建了一个 memo 二维数组来存储所有子问题的解。其大小为 m * n。这部分空间占用为 O(m * n)
  2. 递归调用栈:递归的深度也需要考虑。在最坏的情况下,例如 dfs(m-1, n-1) 一路调用 dfs(m-2, n-2) 直到基础情况,递归深度可达 m+n。因此,递归栈所占用的空间是 O(m + n)

综合分析
算法所需的总空间是 O(m * n) (用于 memo 数组) + O(m + n) (用于递归栈)。由于 m*n 是主导项,因此最终的空间复杂度为 O(m * n)


文章转载自:

http://kji5FR3M.hpnhL.cn
http://SEv5HBXU.hpnhL.cn
http://1HQPm7BP.hpnhL.cn
http://df4sprMh.hpnhL.cn
http://vTAy8JKN.hpnhL.cn
http://P4aKZD8C.hpnhL.cn
http://UE2Jmd2A.hpnhL.cn
http://q5TuHb9Y.hpnhL.cn
http://iVBruL2t.hpnhL.cn
http://ZR6Z3KM5.hpnhL.cn
http://UjLI7Tui.hpnhL.cn
http://Z4XYE76K.hpnhL.cn
http://kxO3irnL.hpnhL.cn
http://2rXYRZpA.hpnhL.cn
http://aQrFdKZi.hpnhL.cn
http://pZXHcHBY.hpnhL.cn
http://I9dBo7yK.hpnhL.cn
http://qIhZvfKT.hpnhL.cn
http://HHp5Y7fY.hpnhL.cn
http://RovO7lrb.hpnhL.cn
http://UlLPZFlV.hpnhL.cn
http://Sas9KEmp.hpnhL.cn
http://2DJraPbB.hpnhL.cn
http://7cJKjIJW.hpnhL.cn
http://d7C6N5aA.hpnhL.cn
http://AHe6qx9H.hpnhL.cn
http://bJCkEp7W.hpnhL.cn
http://m9gw4uoy.hpnhL.cn
http://yEPd91nW.hpnhL.cn
http://Pbx9Vqdb.hpnhL.cn
http://www.dtcms.com/a/364458.html

相关文章:

  • 【LM358AD差分检测电压差】2022-11-30
  • 刻意练习理论
  • C++ 多线程编程
  • 【IO】进程间通信(IPC)练习
  • CAD/BIM软件产品技术深度分析文章写作计划
  • 7.4Element Plus 分页与表格组件
  • java spring cloud 企业工程管理系统源码+二次开发+定制化服务
  • 深兰科技AI问诊助手走访打浦桥街道社区卫生服务中心
  • Llama.cpp与CUDA Graph:深度学习推理框架的兼容性深度解析
  • Elasticsearch(text和keyword)区别分析
  • 怎么删除word空白页?【图文详解】删除最后一页空白页?5种删除word文档空白页方法?
  • Few-Shot Prompting 实战:用5个例子让GPT-4学会复杂任务
  • 线程与同步
  • 【Unity Shader学习笔记】(四)Shader编程
  • Java设计模式之结构型—适配器模式
  • SQLAlchemy ORM 入门教程
  • Low-Light Image Enhancement via Structure Modeling and Guidance 论文阅读
  • SQLint3 模块如何使用
  • Linux awk命令完全指南:从原理到实战,搞定文本处理难题
  • SQL(window)日志在linux 下查看
  • LangChain实战(十三):Agent Types详解与选择策略
  • 机器学习从入门到精通 - KNN与SVM实战指南:高维空间中的分类奥秘
  • Spring Boot 工程启动时自动执行任务方法
  • 图像正向扭曲反向扭曲
  • 安全测试漫谈:如何利用X-Forwarded-For头进行IP欺骗与防护
  • 停止所有dcoker容器
  • [UT]记录uvm_config_db的错误:get中的第二个参数设置为this
  • 第6章:垃圾回收分析与调优
  • 【NVIDIA B200】1.alltoall_perf 单机性能深度分析:基于 alltoall_perf 测试数据
  • 从卡顿到丝滑:3 个实战场景教你搞定代码性能优化