动态规划之编辑距离

Posted 超级码厉

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划之编辑距离相关的知识,希望对你有一定的参考价值。

先祭动态规划模版一套以及编辑距离算法的推导思路,再给出动态规划的概念与思想。


编辑距离在NLP(自然语言处理)中使用比较常见,用来比较两段文本之间的相似性。求解过程使用动态规划思路。

  • 使用场景一:辅助翻译系统的中语料库的处理;有道字典中词条的处理。

  • 使用场景二:智能音箱小爱同学把人语言发出的指令转换成文本后,与后台的指令集进行相似度对比,选出最接近的指令。

  • 使用场景三:DNA序列分析。

  • 更多 ... ...

leetcode原题入下:

Given two words word1 and word2, find the minimum number of steps required to convert word1 to word2. (each operation is counted as 1 step.)

You have the following 3 operations permitted on a word:

a) Insert a character

b) Delete a character

c) Replace a characte



最优子结构分析:假定dp[i][j] 表示word1的前i个字符最少要用几次编辑可以变成word2的前j个字符,有两类情况:

情况一:word1.charAt(i-1) != word2.charAt(j-1)

  • 删除word1的第i个字符,计算word1的前i-1个字符如何变成word2的前j个字符。

       dp[i][j] = dp[i-1][j] + 1

  • 删除word2的第j个字符,计算word1的前i个字符如何变成word2的前j-1个字符。

         dp[i][j] = dp[i][j-1] + 1

  • 替换word1的第i个字符,计算word1的前i-1个字符如何变成word2的前j-1个字符。

       dp[i][j] = dp[i-1][j-1] + 1

  • 替换word2的第j个字符,计算word1的前i个字符如何变成word2的前j-1个字符。

        dp[i][j] = dp[i-1][j-1] + 1

  • 在word1的第i个字符后插入一个字符,使其等于word2的第j个字符

       dp[i][j] = dp[i][j-1] + 1

  • 在word2的第j个字符后插入一个字符,使其等于word1的第i个字符

         dp[i][j] = dp[i-1][j] + 1

情况二:word1.charAt(i-1) == word2.charAt(j-1),

  • 计算word1的前i-1个字符变成word2的前j -1个字符

       dp[i][j] = dp[i-1][j-1], 即,dp[i][j] 计算结果等于上一个子结构dp[i-1][j-1]

  • word1的第i个字符后插入一个字符,使其等于word2的第j个字符

       dp[i][j] = dp[i][j-1] + 1

  • word2的第j个字符后插入一个字符,使其等于word1的第i个字符

        dp[i][j] = dp[i-1][j] + 1

上述两类情况总共9个计算公式有重复,可以合并成四个:

      (1)dp[i][j] = dp[i-1][j-1]

      (2)dp[i][j] = dp[i-1][j] + 1

      (3)dp[i][j] = dp[i][j-1] + 1

      (4)dp[i][j] = dp[i-1][j-1] + 1

因为要计算最小编辑次数,所以状态方程如下:

f[i][j] = Math.min(f[i - 1][j - 1], Math.min(f[i][j - 1] + 1, f[i - 1][j] + 1));

f[i][j] = Math.min(f[i - 1][j - 1] + 1, Math.min(f[i][j - 1] + 1, f[i - 1][j] + 1));


java实现如下:

public class Solution {  public int minDistance(String word1, String word2) {      if(word1 == null && word2 == null) {          return 0;        }      if(word1 == null) {          return word2.length();        }      if(word2 == null) {          return word1.length();        }              int m = word1.length();      int n = word2.length();            //中间结果      int[][] f = new int[m + 1][n + 1];             //初始化              for(int i = 1; i <= m; i++) {          f[i][0] = i;      }      for(int j = 1; j <= n; j++) {          f[0][j] = j;      }            //状态计算      for(int i = 1; i <= m; i++) {          for(int j = 1; j <= n; j++) {            if(word1.charAt(i - 1) == word2.charAt(j - 1)) {                f[i][j] = Math.min(f[i - 1][j - 1], Math.min(f[i][j - 1] + 1, f[i - 1][j] + 1));            } else {                f[i][j] = Math.min(f[i - 1][j - 1] + 1, Math.min(f[i][j - 1] + 1, f[i - 1][j] + 1));            }          }        }      //状态计算      return f[m][n];    }}



动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。利用动态规划算法,可以优雅而高效地解决很多贪婪算法分治算法不能解决的问题。


动态规常见类型:

(1)坐标型动态规划

(2)序列型动态规划

(3)双序列动态规划

(4)划分型动态规划

(5)背包型动态规划

(6)区间型动态规划


动态规划基本思想:

(1)将待求解的问题分解成若干个相互联系的子问题,先求解子问题,然后从这些子问题的解得到原问题的解;对于重复出现的子问题,只在第一次遇到的时候对它进行求解,并把答案存起来,以后遇到直接引用。(过程通常临时存储,所以空间复杂度较高)。

(2)动态规划算法将问题的解决方案视为一系列决策的结果,与贪婪算法不同的是,在贪婪算法中,每采用一次贪婪准则,便做出一个不可撤回的决策;而在动态规划算法中,还要考察每个最优决策序列中是否包含一个最优决策子序列,即问题是否具有最优子结构性质。


动态规划算法的有效性依赖于待求解问题本身具有的两个重要性质:最优子结构性质和子问题重叠性质。

(1)最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。

(2)子问题重叠性质。子问题重叠性质是指在用递归算法对问题进行求解时,每次产生的子问题并不总是新问题,可能会被重复计算。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的解题效率。


当我们已经确定待解决的问题需要用动态规划算法求解时,通常可以按照以下步骤设计动态规划算法:

(1)分析问题的最优解,找出最优解的性质,并刻画其结构特征;

(2)递归地定义最优值;

(3)采用自底向上 / 自顶向下的方式计算问题的最优值;

(4)根据计算最优值时得到的信息,构造最优解。



以上是关于动态规划之编辑距离的主要内容,如果未能解决你的问题,请参考以下文章

动态规划之编辑距离

动态规划之寻找编辑距离

动态规划法算法之编辑距离

376,动态规划之编辑距离

计算字符串的距离 --- 动态规划

leetcode-编辑距离(动态规划)-72