线性DP - 最短编辑距离

153 阅读2分钟
  • 问题背景

    • 给定两个字符串A和B,现在要将A经过若干操作变为B,求将A变为B所需要的最少操作次数,可进行的操作有:
      1. 删除–将字符串A中的某个字符删除。
      2. 插入–在字符串A的某个位置插入某个字符。
      3. 替换–将字符串A中的某个字符替换为另一个字符。
  • Dp分析

Snipaste_2023-03-23_20-28-00.png

  • 状态表示

    • 二维状态表示f(i,j)
    • f(i,j)表示的是哪一个集合:所有满足如下条件的集合
      • 所有将 a[1 ~ i] 变成 b[1 ~ j] 的操作方式
    • f(i,j)存的是什么属性:MaxMin数量;在这里f(i,j)存的应该是最小值,即将 a[1 - i] 变成 b[1- j] 的操作方式的最小值
  • 状态计算:f(i,j)可以怎么算出来?

    • 这里可以将集合分为三个子集
      • 通过一次删除将 a[1 ~ i] 变成 b[1 ~ j]
        • f(i, j) = f(i - 1, j) + 1
      • 通过一次插入将 a[1 ~ i] 变成 b[1 ~ j]
        • f(i, j) = f(i, j - 1) + 1
      • 通过一次替换将 a[1 ~ i] 变成 b[1 ~ j]
        • f(i, j) = f(i -1, j - 1) + 1 或 f(i -1, j - 1)
    • 因此 f(i, j) = min( f(i - 1, j) + 1, f(i, j - 1) + 1, f(i -1, j - 1) + 1 或 f(i -1, j - 1) )
  • 代码

    • //处理边界
      for (int i = 1; i <= n; i++) {
          f[i][0] = i;
      }
      for (int i = 1; i <= m; i++) {
          f[0][i] = i;
      }
      for (int i = 1; i <= n; i++) {
          for (int j = 1; j <= m; j++) {
              f[i][j] = Math.min(f[i - 1][j], f[i][j - 1]) + 1;
              if (a.charAt(i) == b.charAt(j)) {
                  f[i][j] = Math.min(f[i][j], f[i - 1][j - 1]);
              } else {
                  f[i][j] = Math.min(f[i][j], f[i - 1][j - 1] + 1);
              }
          }
      }
      

练习

01 最短编辑距离

  • 题目

Snipaste_2023-03-23_21-05-19.png

  • 题解
import java.io.*;
import java.util.*;

public class Main {
    public static final int N = 1010;
    public static int[][] f = new int[N][N];
    public static int n, m;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        String[] str1 = br.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        String a = " " + br.readLine();
        String[] str2 = br.readLine().split(" ");
        m = Integer.parseInt(str2[0]);
        String b = " " + br.readLine();
        
        //处理边界
        for (int i = 1; i <= n; i++) {
            f[i][0] = i;
        }
        for (int i = 1; i <= m; i++) {
            f[0][i] = i;
        }
        
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                f[i][j] = Math.min(f[i - 1][j], f[i][j - 1]) + 1;
                if (a.charAt(i) == b.charAt(j)) {
                    f[i][j] = Math.min(f[i][j], f[i - 1][j - 1]);
                } else {
                    f[i][j] = Math.min(f[i][j], f[i - 1][j - 1] + 1);
                }
            }
        }
        pw.println(f[n][m]);
        br.close();
        pw.close();
    }
}

02 编辑距离

  • 题目

Snipaste_2023-03-23_23-17-13.png

  • 题解
import java.io.*;
import java.util.*;

public class Main {
    public static final int N = 1010;
    public static int[][] f = new int[N][N];
    public static String[] strs = new String[N];
    public static int n, m;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        String[] str1 = br.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        for (int i = 0; i < n; i++) {
            strs[i] = " " + br.readLine();
        }

        while (m-- > 0) {
            String[] str2 = br.readLine().split(" ");
            String s = " " + str2[0];
            int a = Integer.parseInt(str2[1]);
            int res = 0;
            for (int i = 0; i < n; i++) {
                int q = query(strs[i], s);
                if (q <= a) {
                    res++;
                }
            }
            pw.println(res);
        }
        br.close();
        pw.close();
    }

    public static int query(String a, String b) {
        int l1 = a.length() - 1;
        int l2 = b.length() - 1;
        //处理边界
        for (int i = 1; i <= l1; i++) {
            f[i][0] = i;
        }
        for (int i = 1; i <= l2; i++) {
            f[0][i] = i;
        }

        for (int i = 1; i <= l1; i++) {
            for (int j = 1; j <= l2; j++) {
                f[i][j] = Math.min(f[i - 1][j], f[i][j - 1]) + 1;
                if (a.charAt(i) == b.charAt(j)) {
                    f[i][j] = Math.min(f[i][j], f[i - 1][j - 1]);
                } else {
                    f[i][j] = Math.min(f[i][j], f[i - 1][j - 1] + 1);
                }
            }
        }
        return f[l1][l2];
    }
}