对于重叠子问题呢,前文动态规划解题套路框架 详细介绍过,优化方法无非是备忘录或者 DP table。

备忘录很好加,原来的代码稍加修改即可:

class Solution {
    // 备忘录
    int[][] memo;
        
    public int minDistance(String s1, String s2) {
        int m = s1.length(), n = s2.length();
        // 备忘录初始化为特殊值,代表还未计算
        memo = new int[m][n];
        for (int[] row : memo) {
            Arrays.fill(row, -1);
        }
        return dp(s1, m - 1, s2, n - 1);
    }
 
    int dp(String s1, int i, String s2, int j) {
        if (i == -1) return j + 1;
        if (j == -1) return i + 1;
        // 查备忘录,避免重叠子问题
        if (memo[i][j] != -1) {
            return memo[i][j];
        }
        // 状态转移,结果存入备忘录
        if (s1.charAt(i) == s2.charAt(j)) {
            memo[i][j] = dp(s1, i - 1, s2, j - 1);
        } else {
            memo[i][j] =  min(
                dp(s1, i, s2, j - 1) + 1,
                dp(s1, i - 1, s2, j) + 1,
                dp(s1, i - 1, s2, j - 1) + 1
            );
        }
        return memo[i][j];
    }
 
    int min(int a, int b, int c) {
        return Math.min(a, Math.min(b, c));
    }
}

主要说下 DP table 的解法 首先明确 dp 数组的含义,dp 数组是一个二维数组,长这样:

image.png

有了之前递归解法的铺垫,应该很容易理解。dp[..][0] 和 dp[0][..] 对应 base case,dp[i][j] 的含义和之前的 dp 函数类似:

int dp(String s1, int i, String s2, int j)
// 返回 s1[0..i] 和 s2[0..j] 的最小编辑距离
 
dp[i-1][j-1]
// 存储 s1[0..i] 和 s2[0..j] 的最小编辑距离

dp 函数的 base case 是 i, j 等于 -1,而数组索引至少是 0,所以 dp 数组会偏移一位。

既然 dp 数组和递归 dp 函数含义一样,也就可以直接套用之前的思路写代码,唯一不同的是,DP table 是自底向上求解,递归解法是自顶向下求解

int minDistance(String s1, String s2) {
    int m = s1.length(), n = s2.length();
    // 定义:s1[0..i] 和 s2[0..j] 的最小编辑距离是 dp[i+1][j+1]
    int[][] dp = new int[m + 1][n + 1];
    // base case 
    for (int i = 1; i <= m; i++)
        dp[i][0] = i;
    for (int j = 1; j <= n; j++)
        dp[0][j] = j;
    // 自底向上求解
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (s1.charAt(i-1) == s2.charAt(j-1)) {
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                dp[i][j] = min(
                    dp[i - 1][j] + 1,
                    dp[i][j - 1] + 1,
                    dp[i - 1][j - 1] + 1
                );
            }
        }
    }
    // 储存着整个 s1 和 s2 的最小编辑距离
    return dp[m][n];
}
 
int min(int a, int b, int c) {
    return Math.min(a, Math.min(b, c));
}

经典动态规划:编辑距离 | labuladong 的算法笔记