左神算法 最长公共子串 最长上升子序列

【题目】
给定两个字符串str1和str2,返回两个字符串的最长公共子串
【举例】
String str1 = “1AB2345CD”;
String str2 = “12345EF”;
输出:2345

【要求】
如果str1长度为M,str2长度为N,实现时间复杂度O(M*N),空间复杂度O(1)

这道题还是用动态规划解。状态转移方程和最长公共子序列有所不同,可以对照着理解。
【常规做法】
先维护一个二维数组,dp,这里规定dp的行数是str2的长度,列数是str1的长度。
dp[i][j]表示以str1[j]当做公共子串最后一个字符,str2[i]也当做公共子串最后一个字符的情况下的最长公共子串。
这样就把大问题化简成了小问题,解空间变小了。因为str1和str2整个的公共子串是一个某个str1中的字符和str2中的字符结尾的,具体是哪个我们不知道。但是通过dp我们相当于遍历了所有的情况。
这个跟数列中求最长上升子序列是一样的。在求最长上升子序列中,最长上升子序列是以哪个值作为子序列的结尾,但是我们可以用dp[i]表示以数列中下标为i的字符结尾的最长上升子序列,最后遍历dp所有的值,哪个值最大就是最长上升子序列了,根据一维数组dp的值可以上升子序列是哪些数字组成。dp[i]的递推公式也就好找了,只要比较当前下标i的数字的大小和下标j(j = i-1,i-2,….0)的数字的大小,如果大,就证明dp[i]能在dp[j]的基础上演化过来。注意这里的初始值是1。因为如果没有比任何一个值大,自身也是上升子序列,结果是1。

回到最长公共子串,递推公式就出来了dp[i][j]初始化为0;
str1[j] == str2[i] dp[i][j] = dp[i-1][j-1] + 1

str1[j] != str2[i] dp[i][j] = 0

下面相当于填写二维表
如下图所示,遍历二维表,找出最大的值即为最大公共子串的长度,所在的行,或者列往前推该长度即可得到字符串结果。
这里写图片描述
【改进】空间复杂度是O(1)
二维数组的空间复杂度是O(M*N),通过上表填写的过程后,我们可以总结如下规律。dp[i][j]只上它斜上格有关,而且第一行,第一列的值已经可以确定。那我可以斜着一条一条遍历,时间复杂度没有变,因为我还是遍历完了所有的dp。但是dp[i][j]的值我用dp去存储,每次存的都是斜上方的值,后面的是根据前面的dp值做更新,dp值也做更新。设置max值记录出现的最大的dp值,该值即为最长公共子串的长度。这里我用的是两个for循环遍历斜条,开始从dp[str2.length()-1][0],dp[str2.length()-2][0]….dp[0][0]遍历,然后每一个值都往斜下方走。

for (int i = str2.length() -1; i >= 0 ; i--) {

然后遍历dp[0][1]……. dp[0][str1.length()-1],代码如下

for (int j = 1; j < str1.length(); j++) {

代码如下。

public class Main {
    public static void main(String[] args) {
        String str1 = "1AB2345CD";
        String str2 = "12345EF";
        System.out.println(lcst(str1, str2));
    }
    public static String lcst(String str1, String str2){
        String res = "";
        if (str1 == null || str2 == null || str1.length() == 0 || str1.length() == 0){
            return res;
        }
        int max = -1;
        int index_row = -1;
        int index_col = -1;
        int dp = 0;
        for (int i = str2.length() -1; i >= 0 ; i--) {
            int row = i, col = 0;
            while (row < str2.length() && col < str1.length()){
                if (str2.charAt(row) == str1.charAt(col)){
                   dp += 1;
                }
                else {
                    dp = 0;
                }
                if (max < dp){
                    max = dp;
                    index_row = row;
                    index_col = col;
                }
                row++;
                col++;
            }
        }
        dp = 0;
        for (int j = 1; j < str1.length(); j++) {
            int row = 0, col = j;
            while (row < str2.length() && col < str1.length()){
                if (str2.charAt(row) == str1.charAt(col)){
                    dp += 1;
                }
                else {
                    dp = 0;
                }
                if (max < dp){
                    max = dp;
                    index_row = row;
                    index_col = col;
                }
                row++;
                col++;
            }
        }
        for (int i = 0; i < max; i++) {
            res = str1.charAt(index_col) + res;
            index_col--;
        }
        return res;
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值