题目描述:给定两个字符串,寻找这两个字串之间的最长公共子序列。
子序列:将给定序列中零个或多个元素去掉后的结果
先说一说我对动态规划感想吧,这种想法来源于01背包问题,感觉动态规划难主要是难在递推关系的建立上,即我们不能找到解决问题合适的一面,我不喜欢直接告诉你递推关系是什么,更不喜欢直接上代码,我觉得做题和人类的演进一样,它有一个过程。
抛开时间空间限制先不管,我们首先要寻找一种解决方法的方法,先不去关系它有多个巧妙,往往可以是暴力枚举,就拿这个题老说,如果存在两个字符串A:“ABCFDB”,字符串B:“BFDB”,你是否想过可以求出所有可能出现的情况呢?对于字符串A来说,有A B C F D B AB AC ... BC ABC BCF ....ABCFDB
,对于字符串B来说,有B F D B BF ... DB ... BFDB
,关于这两个集合,找他们共同具备的且长度尽可能长的子序列,就是问题的答案,所以,问题来了,这个问题被我们转化为了如何求出一个字符串的所有子序列,如何在两个子序列的集合中找出公共具备的且长度最长的,
我们先来看第一个问题,如何求出一个字符串的所有子序列,对于没有算法基础的童鞋来说可能确实是个问题,我第一感觉是往全排列的问题上靠,但是全排列问题的解决方法可以理解为是使用回溯法来求解可能出现的所有方法,它本质也可以理解为是一种穷举,当前,若合理的使用限界函数和剪枝,也是一种求解利器。第二个想的是就是背包问题,问什么这么说么,因为一个字符串的子序列是随机组合的,对每个字符来说,有两种选择,要不要加入到目标字符串中,其实本质也是一种回溯,下面简单来看下代码
#include <iostream>
using namespace std;
void printSubSequence(string str,int index,string res){
//到这一种方案已经生成了
if(index == str.length()){
cout<<res<<endl;
return ;
}
//不包含当前字符
printSubSequence(str,index+1,res);
//包含当前字符
printSubSequence(str,index+1,res+str[index]);
}
int main(){
string str = "abcdc";
printSubSequence(str,0,"");
return 0;
}
关于第二个问题:如何在两个子序列的集合中找出公共具备的且长度最长的就比较简单了,可以把第一步求出的结果放到set集合中,从其中一个set集合中取出长度最长的元素,看看第二个集合中有没有,若有则结束,输出次元素的长度,若没有,继续找出长度键1的元素,这样一直比就可以了。
上面的方法确实是一种不错的方法,因为我们已经可以求解出这个问题了,而且每一步的操作都是可以实现的。但是不得不还是要面对的问题,即空间时间问题,对于看上去貌似dp的问题,我们应首先那几个小的短的字符串距离,分析各种情况,这可以stimulate you brain thinking
- 例一:STR1: A B C STR2:A B C,我说最大长度是3,没错,你是怎么看出来的,最无语的回答一眼看出来的,这就是我们解题的老毛病,不能把自己一眼看出的结果转化为可通过有限步的操作而得出的结论,笔记A与A,一样,结果+1,比较B与B一样,结果+1,依次类推,
- 例二:STR1: B C D STR2:A B C,最大长度是2,怎么来的,你还是看出来的么?A与B不相等,这是应该怎么办,大概的想法会有这么几种,一是看C和B,不相等,再看D和C,,,二是虽然B与A不相等,但是B可能与STR2的A后面的元素相等,同理,A可能与STR1的B后面的元素相等,如果你能想到这,这题就应该差不多,如果想不到,也没关系,因为你已经开始意识到有这么一回事了,好呆视野变宽了
从上面的实例中得出,若两个字符相等,则总的公共子序列的长度等于1+后面的字符串所能求得公共子序列的最大长度,若不相等,需要分别讨论,某个字符会不会与另一个字符串中某个字符对应,接下来,就是对这样思想的一种数据抽象化,以dp[i][j]来表示长度为i的字符串与长度为j的字符串的公共子序列长度,这儿可能有些许突兀,还是上面说的,不要为自己想不到而感到愧疚,你的思维正在逐渐的变宽
if(str1[1]==str[1]){
结果等于1+从2~i的str1与2~j的str2的最长公共子序列的长度
}
if(str[1]!=str[2]){
结果等于max(从1~i的str1与2~j的str2的最长公共子序列的长度,从2~i的str1与1~j的str2的最长公共子序列的长度)
}
可以看得出来,整个结果的求解,除知道当前的情况外,我们需要知道从当前点到最后的情况,与其这样,我们是否可以反过来想,即从后往前推,求解dp[i][j],先看str1[i]与str2[j]相等否,则相等,等于前面的情况+1,若不相等,分别考虑以上两种,这个字符会不会跟另一个字符串的前面的字符所匹配,下面是代码,
#include <iostream>
using namespace std;
int main(){
//问题结构分析 递推关系建立 自底向上计算 最优方案追踪
string str1 = "ABCBDAB";
string str2 = "BDCABB";
//dp[i][j]表示str1的从1到i个字符、str2的从1到j个字符的最长公共子序列
/*
if str1[i]==str2[j] = dp[i-1][j-1]+1
dp[i][j] =
if str1[i]!=str2[j] = max(dp[i-1][j],dp[i][j-1])
*/
int ** dp = new int*[str1.length()+1];
for(int i=0;i<=str1.length();i++){
dp[i] = new int[str2.length()+1];
}
for(int i=0;i<=str1.length();i++)
for(int j=0;j<=str2.length();j++)
dp[i][j]=0;
for(int i=1;i<=str1.length();i++){
for(int j=1;j<=str2.length();j++){
if(str1[i-1]!=str2[j-1]){
dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
}else{
dp[i][j] = dp[i-1][j-1]+1;
}
}
}
cout<<dp[str1.length()][str2.length()]<<endl;
for(int i=0;i<=str1.length();i++){
for(int j=0;j<=str2.length();j++)
cout<<dp[i][j]<<" ";
cout<<endl;
}
return 0;
}
说实话,算法很难,但是它也很有趣,当因为自己想不到这样解题而感到忏愧时,甚至觉得自己不适合学算法时,你要你到,你已经在进步了,你的思维正在变得开阔,要知道,当你自己能独立AC下每一道题目时,那种成绩感真的是太美妙了!
共勉!