2-7-2018 String DP


两个String放一起,从某个位置开始看看是否match;要是不match了,就得错开 一位看;要是match了,就都往后挪一位看。在Edit Distance里面,稍微特殊一 点,因为我们还有一个replace操作,可以直接修正当前mismatch,让两个字符串 都挪动一格位置。

于是对于每一个String,就有了一个对于其位置敏感的维度dp[i],代表从最左面开始的i个字符构成的字符串,也是子问题的结构定义。因为我们有两个String并且 需要枚举位置之间可能的各种交错穿插,我们就需要两个维度dp[i][j],代表第一个 字符串中的i个字符和第二个字符串中的j个字符匹配情况。

Longest Common Subsequence

给定长度为m的字符串{1,2,3...m},其subsequences的数量总数为2^m,即对每个字符选择“取/不取”。

  • 如果A的字符与B的字符不match,则我们要看只取A字符情况下的LCS和只取B字符情况下LCS的较大值。
  • 如果A的字符与B的字符match,则我们则是看在不取该字符情况下LCS + 1。
public class Solution {
    /*
     * @param A: A string
     * @param B: A string
     * @return: The length of longest common subsequence of A and B
     */
    public int longestCommonSubsequence(String A, String B) {
        // write your code here
        int n = A.length();
        int m = B.length();

        int[][] dp = new int[n + 1][m + 1];
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= m; ++j) {
                if (A.charAt(i - 1) == B.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[n][m];
    }
}

related problems: Longest Common Substring,

Longest Common Substring

如果match,我就+1,如果不match,我就直接set 0.

public class Solution {
    public int longestCommonSubstring(String A, String B) {
        // write your code here
        if (A == null || B == null) return 0;

        int m = A.length(), n = B.length();

        // A B C D 
        // C B C E 
        // dp[i][j] = dp[i-1][j-1] + 1      if A[i] == B[j]
        //          = 0                     if A[i] != B[j]
        // return max 
        int[][] dp = new int[m + 1][n + 1];
        int max = 0;
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                if (A.charAt(i - 1) != B.charAt(j - 1)) {
                    dp[i][j] = 0;
                } else {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                    max = Math.max(max, dp[i][j]);
                }
            }
        }
        return max;
    }
}

related problem: longest common prefix

longest common prefix

note that, 这道题本身不难,但是需要考虑全面一些conner case。比如说,String array中存在空字符的情况: ["abc","abcd","","ab","ac"],需要特别注意。

public String longestCommonPrefix(String[] strs) {
    // write your code here
    if (strs == null || strs.length == 0) return "";

    int num = 0;

    for (int i = 0; i < strs.length; ++i) {
        if (strs[i].length() == 0) return "";
    }

    for (int i = 0; i < strs[0].length(); ++i) {
        char c = strs[0].charAt(i);
        for (int j = 1; j < strs.length; ++j) {
            if (strs[j].length() < i || strs[j].charAt(i) != c) {
                return strs[0].substring(0, num);
            }
        }
        num++;
    }
    return strs[0].substring(0, num);
}

Edit Distance

对于这道题,我们无法用LCS长度来解决,因为LCS长度和edit distance对字符串结构的利用是不一样的。LCS中mismatch的字符可以出现错位,而edit distance出现要进行修正。算法导论上写的非常清楚,鉴定两条DNA序列的相似度,substring是一种思路(KMP), LCS是一种思路,而edit distance是另一种思路。refer to slides, by Stanford因为对于每个string,我们对其任意操作都会构 造出来一个只与它自己相差一个字符的新string.

  • S[1,2,3..n]为String S;
  • T[1,2,3..m]为String T;
  • Z[1,2...k]为最少修改之后的String Z
    • 注意这里Z可以有多个正解,因为可以有多个最优的正确 操作。类似于LCS,Z也可以是多条路径。

于是;

  • 若S[n] == T[m],则Z[1,2.. k - 1]为S[n - 1]和T[m - 1]的解;
  • 若S[n] != T[m]:
    • 最优解为S[1,2 ..n]与T[1,2 .. m - 1]构造而成,
      • 删掉S[n]
      • OR增加T[m];
    • 最优解为S[1,2 .. n - 1]与T[1,2 .. m]构造而成
      • 删掉T[m]
      • OR增加T[n];
    • 最优解为S[1,2 .. n - 1]和T[1,2 .. m - 1]构造而成
      • 直接把S[n]和T[m] replace成一样的。
class Solution {
    public int minDistance(String word1, String word2) {
        int n = word1.length();
        int m = word2.length();

        int[][] dp = new int[n + 1][m + 1];
        for (int i = 1; i <= n; ++i) {
            dp[i][0] = i;
        }
        for (int i = 1; i <= m; ++i) {
            dp[0][i] = i;
        }

        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= m; ++j) {
                if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else {
                    dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i - 1][j], dp[i][j - 1])) + 1;
                }
            }
        }
        return dp[n][m];
    }
}

one edit distance

basic idea is that 既然有且只有1个位置mismatch,我们可以直接在找到mismatch位置上再判断。

  • 让n,m为两个String的长度

    • 如果m == n,mismatch之后的子字符串一定完全相等; => replace operation

    • 否则长的那段在i之后的substring一定与短的以i开始的substring全等; => delete or insert operation

    • 如果在循环末尾还没有发现mismatch,两个字符串末尾必 然会有1的长度差,否则S == T. => may exist length diff by 1

class Solution {
    public boolean isOneEditDistance(String s, String t) {
        int n = s.length();
        int m = t.length();

        if (Math.abs(n - m) > 1) return false;

        for (int i = 0; i < Math.min(n, m); ++i) {
            if (s.charAt(i) != t.charAt(i)) {
                if (n == m) return s.substring(i + 1).equals(t.substring(i + 1));
                if (n > m) return s.substring(i + 1).equals(t.substring(i));
                if (n < m) return s.substring(i).equals(t.substring(i + 1));
            }
        }
        return Math.abs(n - m) == 1;
    }
}

results matching ""

    No results matching ""