最长公共子序列(Longest Common Subsequence)问题

问题描述

  • 给出两个字符串,找到最长公共子序列(LCS),返回LCS的长度。
  • Example :
    给出”ABCD” 和 “EDCA”,这个LCS是 “A” (或 D或C),返回1
    给出 “ABCD” 和 “EACB”,这个LCS是”AC”返回 2
  • 牛客地址
  • LintCode地址

问题分析

  • 最长公共子序列问题是动态规划经典题目,我将从暴力递归,到记忆化搜索,到动态规划,一步步讲解。
  • 暴力递归
    设置递归函数,用 int findLCS(char[] a, int i, char[] b, int j)返回a[i ~ n]b[j ~ m]的最长公共子序列的长度。

    • 如果a[i] == a[j] ,那么 便应该返回 1 + findLCS(a, i + 1, b, j + 1)
    • 如果a[i] != a[j] ,那么 便应该返回 Math.max(findLCS(a, i + 1, b, j), findLCS(a, i, b, j + 1))

    这是一种将ij 看作起点的逻辑,当然也可以将ij 看作终点, 即用 int findLCS(char[] a, int i, char[] b, int j)返回a[0 ~ i]b[0 ~ j]的最长公共子序列的长度。

  • 记忆化搜索
    因为暴力递归存在大量重复计算,用memory[i][j] 记忆a[i ~ n]b[j ~ m]的最长公共子序列的长度, 初始化都为 -1,如果当前值不为-1,说明已经计算过,无需继续递归,直接返回。否则,则计算,然后将计算后的结果存入memory[i][j]中。
    时间复杂度:O(N^2)

  • 动态规划
    和暴力递归同样的状态,用 dp[i][j] 存储 a[i ~ n]b[j ~ m]的最长公共子序列的长度。根据递归关系,设置状态转移条件。求解。
    当然,也可以用dp[i][j] 来表示a[0 ~ i]b[0 ~ j]的最长公共子序列的长度。

代码实现

  • 暴力递归(TLE)
    public int longestCommonSubsequence(String A, String B) {
        if (A == null || B == null) {
            return 0;
        }
        char[] a = A.toCharArray();
        char[] b = B.toCharArray();
        return findLCS(a, 0, b, 0);
    }
    //返回a[i ~ n] 与 b[j ~ m]的最长公共子序列的长度
    public int findLCS(char[] a, int i, char[] b, int j) {
        if (i == a.length || j == b.length) {
            return 0;
        }
        if (a[i] == b[j]) {
            return 1 + findLCS(a, i + 1, b, j + 1);
        }else {
            return Math.max(findLCS(a, i + 1, b, j), findLCS(a, i, b, j + 1));
        }
    }
  • 记忆化搜索
     public int longestCommonSubsequence(String A, String B) {
        if (A == null || B == null) {
            return 0;
        }
        char[] a = A.toCharArray();
        char[] b = B.toCharArray();
        int [][] memory = new int[a.length][b.length];
        for(int[] nums : memory) {
            Arrays.fill(nums, -1);
        }
        return findLCS(a, 0, b, 0, memory);
    }
    //返回a[i ~ n] 与 b[j ~ m]的最长公共子序列的长度
    public int findLCS(char[] a, int i, char[] b, int j, int[][] memory) {
        if (i == a.length || j == b.length) {
            return 0;
        }
        if (memory[i][j] != -1) {
            return memory[i][j];
        }
        int res = 0;
        if (a[i] == b[j]) {
            res = 1 + findLCS(a, i + 1, b, j + 1, memory);
        }else {
            res =  Math.max(findLCS(a, i + 1, b, j, memory), findLCS(a, i, b, j + 1, memory));
        }
        memory[i][j] = res;
        return res;
    }
  • 动态规划
    public int longestCommonSubsequence(String A, String B) {
        // write your code here
        if (A == null || B == null ) {
            return 0;
        }
        char[] a = A.toCharArray();
        char[] b = B.toCharArray();
        int n = a.length;
        int m = b.length;
        int[][] dp = new int[n + 1][m + 1];
        //dp[i][j] : a[i ~ n] 与 b[j ~ m]的最长公共子序列的长度
        for (int i = n - 1; i >= 0; --i) {
            for (int j = m - 1; j >= 0; --j) {
                if (a[i] == b[j]) {
                    dp[i][j] = 1 + dp[i + 1][j + 1];
                }else {
                    dp[i][j] = Math.max(dp[i + 1][j], dp[i][j + 1]);
                }
            }
        }
        return dp[0][0];
    }
点赞