最长公共子序列(LCS)

原创 2016年08月30日 22:09:40

本来这篇文章是想直接转载过来一篇,然后看看就行了,但是个人总觉得看别人的不如自己动手写过的理解好,所以还是决定把理解的过程记录下来。

LCS问题:

首先先知道LCS问题,这有两种:
1.Longest Common Substiring —- 最长公共子串
2.Longest Common Sequence —- 最长公共子序列

这两者的区别是:前者必须是原字符串中连续的一段
后者可以是在原字符串中随意抽取的一些字符串拼凑成的字符串,只需要遵守顺序即可

也就是说:子串字符的位置必须是连续的,子序列不必连续

求两个子串的最长公共子序列

1.暴力法:

假设两个字符串str1.length = m ,str2.length = n

找出m中所有的子序列,找出n中所有的子序列,然后两者中所有元素一一对比
找出过程需要2^m 和 2^n 的时间复杂度,再一一对比。

即时间复杂度是:O(2^m * 2^n)
空间复杂度:O(2^m + 2^n)

2.动态规划

这种类型的题目考的就是动态规划

这里引用July的一段分析,先分析完再来做题:
事实上,最长公共子序列问题也有最优子结构性质。

Xi=﹤x1,⋯,xi﹥即X序列的前i个字符 (1≤i≤m)(前缀)
Yj=﹤y1,⋯,yj﹥即Y序列的前j个字符 (1≤j≤n)(前缀)

假定Z=﹤z1,⋯,zk﹥∈LCS(X , Y)。

  • 若Xm = Yn (最后一个字符相同),则不难用反证法证明:该字符必定是X与Y的任一最长公共子序列(设其长度为k)的最后一个字符,即是:Zk = Xm = Yn。这就得到:Zk - 1 ∈ LCS(Xm-1 , Yn-1),即Z的前缀Zk-1是Xm-1 与 Ym-1 的最长公共子序列。这样就将问题缩小了,并且可以根据这个递推不断往回:Xm-1 与 Yn-1 的LCS。( LCS(X,Y) 的长度等于LCS(Xm-1,Yn-1) 的长度+1
  • 若Xm ≠ Yn,则不难用反证法证明:要么Z ∈ LCS(Xm-1,Y),要么Z ∈ LCS(X,Yn-1)。由于Zk ≠ Xm 与 Zk ≠ Yn中,至少有一个必然成立,若Zx ≠ Xm则有 Z ∈ LCS(Xm-1,Y) ,类似的,若Zk ≠ Yn,则有Z ∈ LCS(X,Yn-1)。此时,问题转换成求Xm-1 与 Y的LCS,以及 X 与 Yn-1的LCS。LCS(X,Y) 的长度为:max( LCS(Xm-1 , Y) , LCS(X , Yn-1) )。

由于上述当Xm ≠ Yn的情况中,求LCS(Xm-1 , Y) 的长度与LCS(X , Yn-1)的长度,这两个问题不是相互独立的:两者都需要求LCS(Xm-1 , Yn-1)的长度。另外两个序列的LCS中包含了两个序列的前缀的LCS,到这里得出结论:问题具有最优子结构性质,考虑用动态规划法。

也就是说,解决这个LCS问题,你要求三个方面的东西:
1. LCS ( Xm-1,Yn-1 ) +1;
2. LCS ( Xm-1,Y ),LCS ( X,Yn-1 );
3. max ( LCS( Xm-1,Y ) ,LCS( X,Yn-1) )
如下图所示:
这里写图片描述

看完以上的专业的推导,现在来说说咱们自己的理解吧(乡土气息弥漫….),就做这个题目先来:

str1  : B D C A B A          长度为m = 6      
str2  : A B C B D A B        长度为n = 7
找出这两个字符串中的最长公共子序列
  1. 首先,我们申请一个二维数组dp[m] [n],用来存储两个str分别从1~m与1~n的所有长度的串对应的最长公共子序列的长度。
  2. 初始化一些已知的情况,比如:dp[0][0] = 0,dp[i] [0] = dp[0] [j],显而易见,当一个str长度为0,另一个长度无论你为多少,都没有公共子串,长度也就为0.
  3. 之后的值就取决于如上图中的另外两种情况,当str1[i] = str2[j] 的时候,就考虑对角上的前一个元素,在它的基础上+1,得到当前最大,当前元素的前一个对角元素是str1和str2长度都减去1的最优解(我们从开始就是这么定义的,相等在加1,不等就沿用左或上的最优,因为没有加1,那就找各自在对方不变的情况下回退一步的最优(一个长度减1,一个长度不变),即是最优);;在不等的时候,只需要沿用上一次比较中str1[i-1][j] 和 str2[i][j-1]中比较大的一个,意思就是保留之前子串比较中最优的那一个,因为还没有得到最新的最优嘛(不能+1)。那么无论怎么样,我们都得到了当前的最优结果,供下一次再来选择比较。

具体的过程如图下:
这里写图片描述

代码如下:

int Lcs(string s1,string s2){
        int m = s1.length();
        int n = s2.length();
        vector<vector<int> > res(n+1);
        for(int i = 0;i <= n;i++){
                res[i].resize(m+1);
        }
        for(int i = 0;i < m;i++){
                for(int j = 0; j < n;j++){
                        if(s1[i] == s2[j]){
                                res[i+1][j+1] = res[i][j] + 1;
                        }
                        else{
                                res[i+1][j+1] = max(res[i][j+1],res[i+1][j]);
                        }
                }
        }
        for(int i = 0;i <= m;i++){
                for(int j = 0;j <= n;j++){
                        cout<<res[i][j]<<" ";
                }
                cout<<endl;
        }
        cout<<endl;
        return res[m][n];
}

总结:

大概说完这个过程,起码我们能拿到两个字符串立马画出上面这张图吧。
动态规划这种拆分最优子问题的思想个人觉得还是比较难的,尤其是要将一个问题转化成dp,是一件很需要思想的事情,暂时自己的办法是多练,多写,多想,希望在某个时刻脑子开窍了动态规划清晰地像我未来老婆一样在我的脑子里浮现了,有什么错误还请指出,谢谢!

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

最长公共子序列详解及代码实现

最长公共子序列详解及代码实现

面试题精选(74):n个数连接得到最小或最大的多位整数(百度笔试题)

题目描述: 设有n个正整数,将它们联接成一排,组成一个最小的多位整数。 程序输入:n个数 程序输出:联接成的多位数  例如: n=2时,2个整数32,321连接成的最小整数为:32132, n=4时,...

背包问题——“完全背包”详解及实现(包含背包具体物品的求解)

-----Edit by ZhuSenlin HDU         完全背包是在N种物品中选取若干件(同一种物品可多次选取)放在空间为V的背包里,每种物品的体积为C1,C2,…,Cn,与之相对应的...

程序员面试100题之六:最长公共子序列

题目:如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,则字符串一称之为字符串二的子串。注意,并不要求子串(字符串一)的字符必须连续出现在字符串二中。请编写一个函数,输入两个字符串,...

【动态规划】输出所有的最长公共子序列

上篇讲到使用动态规划可以在 θ(mn) 的时间里求出 LCS 的长度,本文将讨论如何输出最长公共子序列。 问题描述:给定两个序列,例如 X = “ABCBDAB”、Y = “BDCABA”,求它们的最...

花生米(三)

原博客:花生米(三) 花生米(三) 时限:1000ms 内存限制:10000K  总时限:3000ms 描述: 五一长假第三天,Tom和Jerry在仓库散步的时候又发现了一堆花生米(仓库,又见仓库……...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)