左神算法 最小编辑代价

【题目】
给定两个字符串str1和str2,在给定三个整数ic,dc和rc,分别代表插入、删除和替换一个字符的代价,返回将str1编辑成str2的最小代价

【举例】str1 = “abc”
str2 = “adc”,ic = 5, dc=3,rc=2
【1】从abc编辑成adc,把b替换成d代价是最小的,返回2
【2】如果rc =100,先删除b,再插入d代价是最小的,返回8
【3】如果str1 == str2 说明不用修改,返回0

用动态规划解题
m = str1.length(), n = str2.length()
申请dp,行数是m+1,列数是n+1。这里我规定str1是行,str2是列。
dp[i][j]代表str1的下标是0….i-1个字符 和 str2的下标是0….j-1字符的最小编辑代价。
如果申请的行列数还是字符串的长度,不加一行做空字符串匹配。那么在还是按之前的动态规划方法做的时候会发现问题,第一行和第一列很难确定它的值。因为有多种选择,第一行有一个str1中的第一个字符了,这时候是选择替换,删除,还是增加呢。状态不好确定。
所以第一行从空字符开始,对于dp[0][j]表示str1还是空串的时候,它去匹配str2的str2[0,1….j-1]字符串,很明显,只有添加的情况,dp[0][j]就是一个等差数列。
同理,第一列就是只有删除的情况。
从dp[1][1]开始填表,dp[i][j]的状态取决于斜上格,左格,和上格。
【犯错】这里我犯了一个错误。
在计算dp[i][j]的时候,在状态转移中如果str1[i-1]和str2[j-1]的字符相等,dp[i][j] = dp[i-1][j-1],这里我认为只有这种情况了。其实是错误的,还是要和左格和上格比较,取最小值。在这题中,我不能保证dp[i-1][j-1]<= dp[i-1][j]和dp[i-1][j-1]<=dp[i][j-1],这两个公式是不能保证成立的。因为dp[i][j-1]也可能从它的斜上方迁跃过来。从图中也可以看到这个公式是不成立的,请看第5行第6列的值4,小于它的左边5。
所以如果相等,还是要继续做比较,我下面的代码就是有问题的。
【问题代码】

public class Main {
    public static void main(String[] args) {
        String str1 = "ab12cd3";
        String str2 = "abcdf";
        int ic = 5, dc = 3, rc =2;
        System.out.println(minCost(str1, str2, ic, dc, rc));
    }

    public static int minCost(String str1, String str2, int ic, int dc, int rc){
        if (str1 == null ||str2 == null || str1.length() == 0 ||str2.length() == 0){
            return 0;
        }
        int[][] dp = new int[str1.length() + 1][str2.length() + 1];
        for (int i = 1; i <= str2.length(); i++) {
            dp[0][i] = dp[0][i-1] + ic;
        }
        for (int i = 1; i <= str1.length() ; i++) {
            dp[i][0] = dp[i-1][0] + dc;
        }
        for (int i = 1; i <=str1.length() ; i++) {
            for (int j = 1; j <= str2.length(); j++) {
                if (str1.charAt(i-1) == str2.charAt(j-1)){
                    dp[i][j] = dp[i-1][j-1];
                }
                else {
                    dp[i][j] = Math.min(dp[i-1][j-1] + rc, Math.min(dp[i][j-1] + ic, dp[i-1][j] + dc));
                }
            }
        }
        return dp[str1.length()][str2.length()];
    }
}

【状态转移】
这里再说明下状态转移,就有三种情况,dp[i][j]:
【1】dp[i][j-1] + ic ,这是str1的当前子串和str2当前子串的最后一个字符之前的子串调整后匹配了,又多了一个str2的字符,为了匹配上,得插入,代价是ic。
【2】dp[i-1][j] + dc ,str1的当前子串的最后一个字符之前的子串和str2当前子串匹配了, 这是str1又多了一个字符,为了匹配上,得删除,代价是dc
【2】dp[i-1][j-1] + rc,str1的当前子串的最后一个字符之前的子串和str2当前子串的最后一个字符之前的子串匹配了,如果str1当前子串的最后一个字符和str2当前子串的最后一个字符不相等,就得加上rc。如果相等,就是dp[i-1][j-1]
【正确代码如下】、

public class Main {
    public static void main(String[] args) {
        String str1 = "ab12cd3";
        String str2 = "abcdf";
        int ic = 5, dc = 3, rc =2;
        System.out.println(minCost(str1, str2, ic, dc, rc));
    }

    public static int minCost(String str1, String str2, int ic, int dc, int rc){
        if (str1 == null ||str2 == null || str1.length() == 0 ||str2.length() == 0){
            return 0;
        }
        int[][] dp = new int[str1.length() + 1][str2.length() + 1];
        for (int i = 1; i <= str2.length(); i++) {
            dp[0][i] = dp[0][i-1] + ic;
        }
        for (int i = 1; i <= str1.length() ; i++) {
            dp[i][0] = dp[i-1][0] + dc;
        }
        for (int i = 1; i <=str1.length() ; i++) {
            for (int j = 1; j <= str2.length(); j++) {
                if (str1.charAt(i-1) == str2.charAt(j-1)){
                    dp[i][j] = dp[i-1][j-1];
                }
                else {
                    dp[i][j] = dp[i-1][j-1] +rc;   //修改之处
                }
                dp[i][j] = Math.min(dp[i][j], Math.min(dp[i][j-1] + ic, dp[i-1][j] + dc));  //修改之处

            }
        }
        return dp[str1.length()][str2.length()];
    }
}

你可能感兴趣的:(数据结构与算法)