动态规划dp函数 + 记忆数组

  • 算法思路
    先定义状态,匹配与不匹配两种。当不匹配时,有三种选择: 插入,删除,替换。
    dp(i, j)为str1[0...i]和str2[0..j]字符串为了匹配做出的代价。
    对于给定的字符串str1,str2,从右到左,即自顶向下进行匹配。会出现两种情况:
    • 当前字符匹配时,dp(i, j)沿用dp(i-1, j-1)的代价。
    • 当前字符不匹配时,有三种选择。
      dp(i, j-1) + 插入的代价
      dp(i-1, j) + 删除的代价
      dp(i-1, j-1) + 替换的代价
      当我们每个选择在向下递归时,只有三种选择,第一个选择结束又返回,进行第二个选择时,必定存在某个选择在当前字符被递归过的情况,因此我们可以利用记忆数组来减少重复的计算。
import java.util.*;
public class Solution {
    //记忆数组来减少递归次数
    int[][] memory;
    /**
     * min edit cost
     * @param str1 string字符串 the string
     * @param str2 string字符串 the string
     * @param ic int整型 insert cost
     * @param dc int整型 delete cost
     * @param rc int整型 replace cost
     * @return int整型
     */
    public int minEditCost (String str1, String str2, int ic, int dc, int rc) {

        int len1 = str1.length();
        int len2 = str2.length();

        memory = new int[len1+1][len2+1];
        //初始化记忆数组
        for(int i = 0; i <= str1.length(); i++){
            for(int j = 0; j <= str2.length(); j++){
                memory[i][j] = -1;
            }
        }
        //自顶向下递归
        return dp(str1, str2, len1, len2, ic, dc, rc);
    }

    /**
    * dp函数
    */
    public int dp(String str1, String str2, int i, int j, int ic, int dc, int rc) {
        //利用记忆数组直接返回
        if(memory[i][j] != -1)
            return memory[i][j];
        if(i == 0) // str1先走完,只能插入str2还没比较过的字符了
            return memory[i][j] = j*ic;
        if(j == 0) // str2先走完, 只能删除str1还没比较过的字符了
            return memory[i][j] = i*dc;
        if(str1.charAt(i-1) == str2.charAt(j-1)) //如果当前字符相等, 直接向前比较
            memory[i][j] = dp(str1, str2, i-1, j-1, ic, dc, rc);
        else{ //比较哪个动作的代价最小
            memory[i][j] = Math.min(Math.min(dp(str1, str2, i, j-1, ic, dc, rc)+ic, dp(str1, str2, i-1, j, ic, dc, rc)+dc), dp(str1, str2, i-1, j-1, ic, dc, rc)+rc);
        }
        return memory[i][j];
    }
}
  • 算法复杂度
    • 时间:O(2^N)。自顶向下的递归深度。
    • 空间:O(N*M)。二维数组的空间。

递推 dp数组

  • 算法思路
    可以用递推的解法,dp数组的含义跟dp函数一样,不同的是,dp数组采用的是自底向上的解法,递归采用的是自顶向下的解法。
import java.util.*;

public class Solution {
    /**
     * min edit cost
     * @param str1 string字符串 the string
     * @param str2 string字符串 the string
     * @param ic int整型 insert cost
     * @param dc int整型 delete cost
     * @param rc int整型 replace cost
     * @return int整型
     */
    public int minEditCost (String str1, String str2, int ic, int dc, int rc) {
        // write code here
        int n = str1.length(), m = str2.length();
        int[][] dp = new int[n+1][m+1];
        for(int i = 1; i <= n; i++){
           dp[i][0] = i*dc; 
        }
        for(int j = 1; j <= m; j++){
           dp[0][j] = j*ic;
        }
        for(int i = 1; i <= n; i++){
           for(int j = 1; j <= m; j++){
               if(str1.charAt(i-1) == str2.charAt(j-1))
                   dp[i][j] = dp[i-1][j-1];
               else
                   dp[i][j] = min(dp[i-1][j]+dc, dp[i][j-1]+ic, dp[i-1][j-1]+rc);
            }
        }
        return dp[n][m];
    }

    public int min(int a, int b, int c){
        return Math.min(a, Math.min(b, c));
    }
}
  • 算法复杂度
    • 时间复杂度:O(N*M)。其中 m 为 str2 的长度,n 为 str1的长度。
    • 空间复杂度: O(N*M)。二维数组的空间。