【每日算法】最长公共子序列LCS

学而不思则罔,思而不学则殆


题目

两个数组的,两个数组中都包含的最长序列

最长公共子序列,全称Longest Common Subsequence,简称LCS

基本概念

这里需要了解一下子序列和字=子串的概念。

类别相同点不同点
子序列元素保持跟父序列一致元素可以不连续
子串元素保持跟父序列一致元素必须连续

可以说子串一定是子序列,但是子序列不一定是子串。
如图,更加形象一点:
在这里插入图片描述
在这里插入图片描述

算法思路

该问题我们首先想到用暴力算法解决。但是发现暴力算法不行,一个长度为n的数组的序列个数是2的n次方,两个数组求左右的子序列在一一对比是否相等,那么事件算法复杂度为(n,m分别为两个数组的长度): O ( 2 n ∗ 2 m ) O(2^n*2^m) O(2n2m)
这么看的话,时间复杂度太恐怖了,这条路走不通。
最长公共子序列算法在算法导论上有详细讲解,这里简单说一下核心思想。
假定两个序列为X={x1, x2, …, xn}和Y={y1, y2, …, ym),并设Z={z1, z2, …, zk}为X和Y的任意一个LCS。
C [ i , j ] = { 0 if i=0 or j=0 C [ i − 1 , j − 1 ] + 1 if i,j>0 and x[i] = y[j]  m a x ( C [ i − 1 , j ] , C [ i , j − 1 ] ) if i,j>0 and x[i] != y[j]  C[i,j]= \begin{cases} 0 & \text {if i=0 or j=0}\\ C[i-1,j-1]+1 & \text {if i,j>0 and x[i] = y[j] }\\ max(C[i-1,j],C[i,j-1]) & \text {if i,j>0 and x[i] != y[j] }\\ \end{cases} C[i,j]=0C[i1,j1]+1max(C[i1,j],C[i,j1])if i=0 or j=0if i,j>0 and x[i] = y[j] if i,j>0 and x[i] != y[j] 

1.如果X[n] == Y[m],则Z[k] == X[n] == Y[m] 且 且Z[k-1]是Xn-1和Ym-1的一个LCS。
2.如果X[n] != Y[m],则Z[k] != X[n] 蕴含Z是X[n-1]和Y得一个LCS。
3.如果X[n] != Y[m],则Z[k] != Y[m] 蕴含Z是Y[m-1]和X得一个LCS。

简单来说:

假如X的最后一个元素 与 Y的最后一个元素相等,那么X和Y的LCS就等于 {X减去最后一个元素} 与 {Y减去最后一个元素} 的 LCS 再加上 X和Y相等的最后一个元素。

假如X的最后一个元素 与Y的最后一个元素不等,那么X和Y的LCS就等于 : {X减去最后一个元素} 与 Y的LCS, {Y减去最后一个元素} 与 X 的LCS 中的最大的那个序列。

图解计算LCS

以X={2,4,5,6,7,8,8,9} Y={4,6,8,5,9,6,8,9,3}为例。

初始化

当i or j = 0 的时候C[i,j] = 0
在这里插入图片描述
然后一行一行的填写数据,根据递推公式:

第一行

在这里插入图片描述

第二行

在这里插入图片描述

第三行

在这里插入图片描述

第四行

在这里插入图片描述

第五行

在这里插入图片描述

第六行

在这里插入图片描述

第七行

在这里插入图片描述

第八行

在这里插入图片描述

第九行

在这里插入图片描述

构造LCS

在这里插入图片描述

我们根据递推公式建立上面的表格:

  1. C[9,8] = 5 且Y[9] != X[8],所以倒退回去,C[9,8]的值来源于C[8,8],(因为C[8,8]>C[9,7])
  2. C[8,8] = 5 且Y[8] == X[8],所以倒推回去,c[8][8]的值来源于 c[7][7]
  3. 然后以此类推,得出LCS{4,5,6,8,9}
  4. 在第一步的时候,如果遇到Y[i]!=X[j],当时C[i-1,j] = C[i,j-i]这种分支情况,如果不是求所有的LCS,可以只选择左走或者上走,如果要求所有的LCS就需要两个分支都考虑。

复杂度

时间复杂度

构建表时间复杂度为: O ( m n ) O(mn) O(mn)
输出LCS: O ( m + n ) O(m+n) O(m+n)
整体复杂度为: O ( m n ) O(mn) O(mn)

空间复杂度

辅助二维数组: O ( m n ) O(mn) O(mn)

Demo

    public static void main(String[] args) {
        //随机产生两个数组
        //X={2,4,5,6,7,8,8,9} Y={4,6,8,5,9,6,8,9,3}
        int1 = new int[]{2, 4, 5, 6, 7, 8, 8, 9};
        int2 = new int[]{4, 6, 8, 5, 9, 6, 8, 9, 3};
        lcs(int1, int2);
    }

    private static void lcs(int[] X, int[] Y) {
        //辅助数据
        int[][] flag = new int[Y.length + 1][X.length + 1];
        //构建辅助表
        for (int y = 0; y < Y.length; y++) {
            for (int x = 0; x < X.length; x++) {
                //x == y
                if (Y[y] == X[x]) {
                    flag[y + 1][x + 1] = flag[y][x] + 1;
                } else {
                    flag[y + 1][x + 1] = Math.max(flag[y][x + 1], flag[y + 1][x]);
                }
            }
        }

        //打印
        for (int y = 0; y < flag.length; y++) {
            System.out.println(Arrays.toString(flag[y]));
        }

        //计算LCS
        int lcsNum = flag[Y.length][X.length];
        int[] lcs = new int[lcsNum];
        int lcsIndex = lcsNum;
        for (int x = X.length, y = Y.length; x > 0 && y > 0 && lcsIndex > 0; ) {
            //x y 是否相等
            System.out.println(Y[y - 1] + " " + X[x - 1]);
            //X[x] == Y[y]
            if (X[x - 1] == Y[y - 1]) {
                int index = --lcsIndex;
                lcs[index] = X[x - 1];
                System.out.println("index:" + index + " " + Arrays.toString(lcs));
                x--;
                y--;
            } else {//X[x] != Y[y]
                //得出最大值,这里只计算其中一情况,没有考虑相等的情况
                if (flag[y - 1][x] > flag[y][x - 1]) {
                    y--;
                } else {
                    x--;
                }
            }
        }
        System.out.println("lcs:" + Arrays.toString(lcs));
    }

log信息:

[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 1, 1, 1, 1, 1, 1, 1]
[0, 0, 1, 1, 2, 2, 2, 2, 2]
[0, 0, 1, 1, 2, 2, 3, 3, 3]
[0, 0, 1, 2, 2, 2, 3, 3, 3]
[0, 0, 1, 2, 2, 2, 3, 3, 4]
[0, 0, 1, 2, 3, 3, 3, 3, 4]
[0, 0, 1, 2, 3, 3, 4, 4, 4]
[0, 0, 1, 2, 3, 3, 4, 4, 5]
[0, 0, 1, 2, 3, 3, 4, 4, 5]
3 9
9 9
index:4 [0, 0, 0, 0, 9]
8 8
index:3 [0, 0, 0, 8, 9]
6 8
6 7
6 6
index:2 [0, 0, 6, 8, 9]
9 5
5 5
index:1 [0, 5, 6, 8, 9]
8 4
6 4
4 4
index:0 [4, 5, 6, 8, 9]
lcs:[4, 5, 6, 8, 9]

Process finished with exit code 0

最长子序列的应用

  1. 求最长(非)递增子序列
  2. 求最长(非)递减子序列

主要思路:根据原序列建立一个辅助序列,然后辅助序列跟原序列求最长公共子序列。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值