最长公共子序列(LCS)动态规划解题笔记

最长公共子序列(LCS)动态规划解题笔记

参考:
动态规划解最长公共子序列问题
动态规划 最长公共子序列 过程图解
动态规划基础篇之最长公共子序列问题

题意

子序列和最子串的区别在于子串需要连续,但子序列不需要,但仍需要保持顺序,可以理解为在原字符串中删除若干字符,剩下的序列就是子序列。
最长公共子序列,即两个字符串的所有子序列中最长的一个,当然可能会出现不止一个最长子序列的情况。

思路

一个关键点是公共子序列的长度,公共子序列有很多,求最长的,需要保存一下子序列的长度。
对于长度分别为len1和len2的两个字符串str1和str2,动态规划的着眼点在于,对于str1的下标i和str2的下标j:
- 如果两个字符串在相应上的字符相同,那么它们的最长公共子序列就是之前的最长公共子序列加上该字符,最大长度+1;
- 如果字符不相同,那么最长公共子序列就是以下二者中的长度最长的一个:
- str1[0:i]和str2[0:j-1]的最长公共子序列
- str1[0:i-1]和str2[0:j]的最长公共子序列
- 如果长度相等,走哪个分支都一样,但是可能对应不同的子序列,所以最长公共子序列并不唯一

这里写图片描述

同时在比较的过程中,可以得知每次的最长公共子序列的偏向(上面二者中的哪一个,即,虽然所有子序列的字符在两个字符串中都有,但当前添加的最后一个字符是明显来自于某个字符串的下标位置字符的(或者二者相等)),在循环结束后,可以据此从最后递归倒推到头部输出最大公共子序列。

代码实现

假设str1和str2的下标分别用i,j表示,创建两个二维数组,一个用于保存str1[0:i]和str2[0:j]的最长公共子序列的长度,另一个则表示在遍历过程中每次取的是哪一边的最长长度:

import java.lang.Math;
public class Test {
    static int[][] lens;
    static int[][] ores;
    static String result = "";
    public static String lcs(String x, String y) {
        // your code here
        int len1 = x.length();
        int len2 = y.length();
        // 因为要根据i-1和j-1位置的值来推断当前长度,数组需要比字符串长度大一号,
        // 并且把第i行和第j列都设为0,循环时从1开始,对应字符串的i-1和j-1位。
        lens = new int[len1+1][len2+1];
        ores = new int[len1+1][len2+1];
        for(int i = 0; i <= len1; i++) {
            lens[i][0] = 0;        
        }
        for(int j = 0; j <= len2; j++) {
            lens[0][j] = 0;        
        }
        for (int i = 1; i <= len1=; i++) {
            for (int j = 1; j <= len2=; j++) {
                // 获取该位置上的两个数字
                int xi = Integer.valueOf(x.charAt(i - 1));
                int yj = Integer.valueOf(y.charAt(j - 1));
                if (xi == yj) {
                    // 如果相同,最长公共子序列长度加一,没有偏向,可以直接输出该位置的字符
                    // 也就是x[0:i]相对于x[0:i-1],y[0:j]相对于y[0:j-1]都多了一个公共字符
                    lens[i][j] = lens[i-1][j-1] + 1;
                    ores[i][j] = 0;
                } else if (lens[i][j-1] > lens[i-1][j]){
                    // 如果x[0:i]和y[0:j-1]的lsc较长,则赋值给它,并设置偏向
                    // 说明x[0:i]和y[0:j-1]相对于x[0:i-1]和y[0:j]多了一个公共字符
                    lens[i][j] = lens[i][j-1];
                    ores[i][j] = 1;
                } else {
                    // 反之如果x[0:i-1]和y[0:j]的lsc较长,则赋值给它,并设置偏向
                    // 说明x[0:i-1]和y[0:j]相对于x[0:i]和y[0:j-1]多了一个公共字符
                    lens[i][j] = lens[i-1][j];
                    ores[i][j] = -1;
                }
            }
        }
//         输出
        return printLCS(x, len1, len2);
    }

    public static void main(String[] args) {
        lcs("132535365", "123456789");
    }

    static String printLCS(String x, int i, int j) {
        if (i == 0 || j == 0) {
            return "";
        } 
        if (ores[i][j] == 0) {
            // 直接添加该位置上的字符
            result = printLCS(x, i - 1, j - 1) + String.valueOf(x.charAt(i-1));
        } else if (ores[i][j] == 1) {
            // 说明x[0:i]和y[0:j-1]相对于x[0:i-1]和y[0:j]多了一个公共字符
            // 因此保持i不变,j-1继续递归
            result = printLCS(x, i, j - 1) ;
        } else {
            // 说明x[0:i-1]和y[0:j]相对于x[0:i]和y[0:j-1]多了一个公共字符
            // 因此保持j不变,i-1继续递归
            result = printLCS(x, i - 1, j) ;  
        }
        return result;
    }
}

一些优化解法(来自codewar其他人的解法)

public static String lcs(String x, String y) {
  // your code here
    int m = x.length(), n = y.length();
    int[][] nums = new int[m + 1][n + 1];
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            nums[i][j] = nums[i - 1][j - 1] + (x.charAt(i - 1) == y.charAt(j - 1) ? 1 : 0);
            nums[i][j] = Math.max(nums[i][j], nums[i - 1][j]);
            nums[i][j] = Math.max(nums[i][j], nums[i][j - 1]);
        }
    }
    StringBuilder sb = new StringBuilder();
    for(int i = 1; i <= n; i++) {
        if (nums[m][i] - nums[m][i - 1] == 1) {
            sb.append(y.charAt(i - 1));
        }
    }
    return sb.toString();
}

public static String lcs(String x, String y) {
    if (x.isEmpty() || y.isEmpty())
        return new String();
    if (x.charAt( x.length() - 1 ) == y.charAt( y.length() - 1 ))
        return lcs( x.substring( 0, x.length() - 1 ),
                y.substring( 0, y.length() - 1 ) ) + x.charAt( x.length() - 1 );
    String answerFory = lcs( x, y.substring( 0, y.length() - 1 ) );
    String answerForx = lcs( x.substring( 0, x.length() - 1 ), y );
    return answerFory.length() > answerForx.length() ? answerFory : answerForx;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值