zoukankan      html  css  js  c++  java
  • LeetCode1143. 最长公共子序列

    ☆☆☆☆思路:Longest Common Sequence (LCS) 问题。要对两个字符串进行扫描,因此需要两个维度进行动态规划。

      1. 状态的定义:dp[m][n] 表示 S1[0...m] 和 S2[0...n] 的最长公共子序列的长度。

      2. 状态转移:每次对最新需要考虑的字符,s1[m] 和 s2[n]

          ① 如果s1[m] == s2[n],则 dp[m][n] = 1 + dp[m-1][n-1]

          ②如果s1[m] != s2[n],则 dp[m][n] = max {dp[m-1][n], dp[m][n-1]}             (往前缩一个)

      3. 初始化:对于dp矩阵(m*n)的第一列,需要注意一旦dp[i][0]被设置为1,那么之后的dp[i+1...m][0]也都为1; dp矩阵的第一行,同理。

      4. 结果:从左到右,再从上到下计算dp矩阵,右下角的值即为所求。

    代码1:

    class Solution {
        public int longestCommonSubsequence(String text1, String text2) {
            // 状态的定义:dp[m][n]表示s1[0...m] 和 s2[0...n] 的最长公共子序列的长度
            int m = text1.length(), n = text2.length();
            int[][] dp = new int[m][n];
            dp[0][0] = text1.charAt(0) == text2.charAt(0) ? 1 : 0;
            for (int i = 1; i < m; i++) {
                dp[i][0] = Math.max(dp[i-1][0], text1.charAt(i) == text2.charAt(0) ? 1 : 0); // 注意还需要考虑以前的
            }
            for (int j = 1; j < n; j++) {
                dp[0][j] = Math.max(dp[0][j-1], text1.charAt(0) == text2.charAt(j) ? 1 : 0); // 注意还需要考虑以前的
            }
            for (int i = 1; i < m; i++) {
                for (int j = 1; j < n; j++) {
                    if (text1.charAt(i) == text2.charAt(j)) {
                        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[m-1][n-1];
        }
    }

    代码2(如果只求长度,参考“矩阵的最小路径和”,空间复杂度可压缩为O(min{M, N}))

    class Solution {
        public int longestCommonSubsequence(String text1, String text2) {
            /**
             *  空间压缩:
             *      二维dp数组中每个格子内的值会依赖它的左边,上边和左上边,和其他的值无关。
             *      而左上角的值有可能会被上一步计算的时候就被替换掉了,所以必须要先保存下来。
             */
            int m = text1.length(), n = text2.length();
            int[] dp = new int[n];
            // 初始化第一行
            dp[0] = text1.charAt(0) == text2.charAt(0) ? 1 : 0;
            for (int j = 1; j < n; j++) {
                dp[j] = Math.max(dp[j-1], text1.charAt(0) == text2.charAt(j) ? 1 : 0);
            }
            for (int i = 1; i < m; i++) {
                // 单独算第一列
                int pre = dp[0];
                dp[0] = Math.max(dp[0], text1.charAt(i) == text2.charAt(0) ? 1 : 0);
                for (int j = 1; j < n; j++) {
                    int temp = dp[j];  // dp[j] 这个值会被替换,所以替换之前要把他保存下来
                    if (text1.charAt(i) == text2.charAt(j)) {
                        dp[j] = pre + 1;  // 左上
                    }else {
                        dp[j] = Math.max(dp[j-1], dp[j]);  // 左 或者 上
                    }
                    pre = temp;
                }
            }
            return dp[n-1];
        }
    }

    【举一反三】:除了求出最长公共子序列的长度,还需要会根据dp数组求出最长公共子序列是什么!-----------参考左神书P210

    思路为根据dp状态还原出求解dp的过程,来自哪个策略就朝哪个方向移动。具体方法为:

      1. 从dp矩阵右下角开始,有三种移动方式:向上,向左,向左上。设移动过程中, i 表示此时的行数, j 表示此时的列数,同时 res 表示最长公共子序列;

      2. 如果 dp[i][j] 大于 dp[i-1][j] 和 dp[i][j-1],说明之前在计算dp[i][j] 时,一定是选择了决策 dp[i-1][j-1] + 1,可以确定str[1]等于str[2],并且这个字符一定属于最长公共子序列,放入res,然后向左上方移动;

      3. 如果 dp[i][j]  等于dp[i-1][j] , 说明之前在计算dp[i][j] 时, dp[i-1][j-1] + 1这个决策不是必须选择的决策,向上方移动;

      4.  如果 dp[i][j]  等于dp[i][j-1],与步骤3同理,向左方移动

      5. 如果 dp[i][j] 等于 dp[i-1][j] 和 dp[i][j-1],向上还是向下无所谓,选择其一即可, 反正不会错过必须选择的字符。

        private String lcse(String s1, String s2) {
            if (s1 == null || s2 == null || s1.equals("") || s2.equals("")) {
                return "";
            }
            int[][] dp = getdp(s1, s2);
            int m = s1.length() - 1, n = s2.length() - 1;
            char[] res = new char[dp[m][n]];
            int index = res.length - 1;
            while (index >= 0) {
                if (n > 0 && dp[m][n] == dp[m][n-1]) {
                    n --;
                }else if (m > 0 && dp[m][n] == dp[m-1][n]) {
                    m --;
                }else {
                    res[index--] = s1.charAt(m);
                    m --;
                    n --;
                }
            }
            return String.valueOf(res);
        }
        public int[][] getdp(String text1, String text2) {
            // 状态的定义:dp[m][n]表示s1[0...m] 和 s2[0...n] 的最长公共子序列的长度
            int m = text1.length(), n = text2.length();
            int[][] dp = new int[m][n];
            dp[0][0] = text1.charAt(0) == text2.charAt(0) ? 1 : 0;
            for (int i = 1; i < m; i++) {
                dp[i][0] = Math.max(dp[i-1][0], text1.charAt(i) == text2.charAt(0) ? 1 : 0);
            }
            for (int j = 1; j < n; j++) {
                dp[0][j] = Math.max(dp[0][j-1], text1.charAt(0) == text2.charAt(j) ? 1 : 0);
            }
            for (int i = 1; i < m; i++) {
                for (int j = 1; j < n; j++) {
                    if (text1.charAt(i) == text2.charAt(j)) {
                        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;
        }

    【复杂度分析】:总的时间复杂度为O(M*N),空间复杂度为O(M*N)。  如果只求长度,空间复杂度可压缩为O(min{M, N})

      1. 求dp矩阵的某个位置就是简单比较值,时间复杂度为O(1),由于dp大小为M*N,所以求dp矩阵的时间复杂度为O(M*N)

      2. 通过dp得到最长公共子序列的过程为O(M+N),因为向左最多移动N个位置,向上最多移动M个位置。

      3. 因此,总的时间复杂度为O(M*N)

  • 相关阅读:
    JS新API标准 地理定位(navigator.geolocation
    微信公众号菜单
    js选择权
    mui 弹框
    又拍云
    弹框
    sublime插件
    将Apache的.htaccess转换到nginx中
    php 图片上传类
    C# 使用Com组件正确的释放方法
  • 原文地址:https://www.cnblogs.com/HuangYJ/p/14232685.html
Copyright © 2011-2022 走看看