序列试题---最大子序列、最长递增子序列、最长公共子串、最长公共子序列、字符串编辑距离 .
最大子序列是要找出由数组成的一维数组中和最大的连续子序列。比如{5,-3,4,2}的最大子序列就是 {5,-3,4,2},它的和是8,达到最大;而 {5,-6,4,2}的最大子序列是{4,2},它的和是6。你已经看出来了,找最大子序列的方法很简单,只要前i项的和还没有小于0那么子序列就一直向后扩展,否则丢弃之前的子序列开始新的子序列,同时我们要记下各个子序列的和,最后找到和最大的子序列。
代码如下:
#include<iostream>
using namespace std;
int MaxSubSeq( const int *arr, int len, int *start, int *end){
int max=0; //记录目前找到的最大子序列的和
int sum=0; //记录当前子序列的和
int begin=0,finish=0; //记录当前子序列的起始下标
*start=begin;*end=finish; //记录最长子序列的起始下标
for ( int i=0;i<len;i++){
sum+=arr[i];
finish=i;
if (sum>max){
max=sum;
*end=finish;
*start=begin;
}
if (sum<=0){
sum=0;
begin=i+1;
}
}
return max;
}
int main(){
int arr[6]={5,-3,-2,12,9,-1};
int start,end;
int max=MaxSubSeq(arr,6,&start,&end);
cout<< "The MaxSubSeq is from position " <<start<< "to position " <<end<< "." <<endl;
cout<< "Sum of MaSubSeq: " <<max<<endl;
return 0;
}
|
最长递增子序列
比如arr={1,5,8,2,3,4}的最长递增子序列是1,2,3,4
动态规划的思想,考虑{arr[0],...,arr[i]}的最长递增子序列时需要找到所有比arr[i]小的arr[j],且j<i,结果应该是所有{arr[0],...,arr[j]}的最长递增子序列中最长的那一个再加1。即我们需要一个辅助的数组aid_arr,aid_arr[i]的值是{arr[0],...,arr[i]}的最长递增子序列的长度,aid_arr[0]=1。
#include<iostream>
#include<stack>
#include<vector>
using namespace std;
int main(){
const int len=14;
int arr[len]={1,9,3,8,11,4,5,6,4,1,9,7,1,7};
vector< int > vec(&arr[0],&arr[len]);
vector< int > monoseqlen(len,1);
vector< int > preindex(len,-1);
int maxmonoseqlen=-1;
int maxmonoindex=-1;
for ( int i=1;i<len;++i){
int curr=vec[i];
for ( int j=0;j<i;++j){
if (vec[j]<vec[i]){
int msl=monoseqlen[j]+1;
if (msl>monoseqlen[i]){
monoseqlen[i]=msl;
preindex[i]=j;
}
}
}
}
for ( int i=0;i<len;++i){
if (monoseqlen[i]>maxmonoseqlen){
maxmonoseqlen=monoseqlen[i];
maxmonoindex=i;
}
}
stack< int > st;
while (maxmonoindex>=0){
st.push(vec[maxmonoindex]);
maxmonoindex=preindex[maxmonoindex];
}
vector< int > rect;
while (!st.empty()){
rect.push_back(st.top());
st.pop();
}
vector< int >::iterator itr=rect.begin();
while (itr!=rect.end()){
cout<<*itr++<< "\t" ;
}
cout<<endl;
return 0;
}
|
最长公共子串(LCS)
找两个字符串的最长公共子串,这个子串要求在原字符串中是连续的。其实这又是一个序贯决策问题,可以用动态规划来求解。我们采用一个二维矩阵来记录中间的结果。这个二维矩阵怎么构造呢?直接举个例子吧:"bab"和"caba"(当然我们现在一眼就可以看出来最长公共子串是"ba"或"ab")
b a b
c 0 0 0
a 0 1 0
b 1 0 1
a 0 1 0
我们看矩阵的斜对角线最长的那个就能找出最长公共子串。
不过在二维矩阵上找最长的由1组成的斜对角线也是件麻烦费时的事,下面改进:当要在矩阵是填1时让它等于其左上角元素加1。
b a b
c 0 0 0
a 0 1 0
b 1 0 2
a 0 2 0
这样矩阵中的最大元素就是 最长公共子串的长度。
在构造这个二维矩阵的过程中由于得出矩阵的某一行后其上一行就没用了,所以实际上在程序中可以用一维数组来代替这个矩阵。
代码如下:
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
//str1为横向,str2这纵向
const string LCS( const string& str1, const string& str2){
int xlen=str1.size(); //横向长度
vector< int > tmp(xlen); //保存矩阵的上一行
vector< int > arr(tmp); //当前行
int ylen=str2.size(); //纵向长度
int maxele=0; //矩阵元素中的最大值
int pos=0; //矩阵元素最大值出现在第几列
for ( int i=0;i<ylen;i++){
string s=str2.substr(i,1);
arr.assign(xlen,0); //数组清0
for ( int j=0;j<xlen;j++){
if (str1.compare(j,1,s)==0){
if (j==0)
arr[j]=1;
else
arr[j]=tmp[j-1]+1;
if (arr[j]>maxele){
maxele=arr[j];
pos=j;
}
}
}
// {
// vector<int>::iterator iter=arr.begin();
// while(iter!=arr.end())
// cout<<*iter++;
// cout<<endl;
// }
tmp.assign(arr.begin(),arr.end());
}
string res=str1.substr(pos-maxele+1,maxele);
return res;
}
int main(){
string str1( "21232523311324" );
string str2( "312123223445" );
string lcs=LCS(str1,str2);
cout<<lcs<<endl;
return 0;
}
|
最长公共子序列
最长公共子序列与最长公共子串的区别在于最长公共子序列不要求在原字符串中是连续的,比如ADE和ABCDE的最长公共子序列是ADE。
我们用动态规划的方法来思考这个问题如是求解。首先要找到状态转移方程:
等号约定,C1是S1的最右侧字符,C2是S2的最右侧字符,S1‘是从S1中去除C1的部分,S2'是从S2中去除C2的部分。
LCS(S1,S2)等于下列3项的最大者:
(1)LCS(S1,S2’)
(2)LCS(S1’,S2)
(3)LCS(S1’,S2’)--如果C1不等于C2; LCS(S1',S2')+C1--如果C1等于C2;
边界终止条件:如果S1和S2都是空串,则结果也是空串。
下面我们同样要构建一个矩阵来存储动态规划过程中子问题的解。这个矩阵中的每个数字代表了该行和该列之前的LCS的长度。与上面刚刚分析出的状态转移议程相对应,矩阵中每个格子里的数字应该这么填,它等于以下3项的最大值:
(1)上面一个格子里的数字
(2)左边一个格子里的数字
(3)左上角那个格子里的数字(如果 C1不等于C2); 左上角那个格子里的数字+1( 如果C1等于C2)
举个例子:
G C T A
0 0 0 0 0
G 0 1 1 1 1
B 0 1 1 1 1
T 0 1 1 2 2
A 0 1 1 2 3
填写最后一个数字时,它应该是下面三个的最大者:
(1)上边的数字2
(2)左边的数字2
(3)左上角的数字2+1=3,因为此时C1==C2
所以最终结果是3。
在填写过程中我们还是记录下当前单元格的数字来自于哪个单元格,以方便最后我们回溯找出最长公共子串。有时候左上、左、上三者中有多个同时达到最大,那么任取其中之一,但是在整个过程中你必须遵循固定的优先标准。在我的代码中优先级别是左上>左>上。
下图给出了回溯法找出LCS的过程:
奉上代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
#include<iostream>
#include<cstring>
#include<stack>
#include<utility>
#define LEFTUP 0
#define LEFT 1
#define UP 2
using namespace std;
int Max( int a, int b, int c, int *max){ //找最大者时a的优先级别最高,c的最低.最大值保存在*max中
int res=0; //res记录来自于哪个单元格
*max=a;
if (b>*max){
*max=b;
res=1;
}
if (c>*max){
*max=c;
res=2;
}
return res;
}
//调用此函数时请注意把较长的字符串赋给str1,这主要是为了在回溯最长子序列时节省时间。如果没有把较长的字符串赋给str1不影响程序的正确执行。
string LCS( const string &str1, const string &str2){
int xlen=str1.size(); //横向长度
int ylen=str2.size(); //纵向长度
if (xlen==0||ylen==0) //str1和str2中只要有一个为空,则返回空
return "" ;
pair< int , int > arr[ylen+1][xlen+1]; //构造pair二维数组,first记录数据,second记录来源
for ( int i=0;i<=xlen;i++) //首行清0
arr[0][i].first=0;
for ( int j=0;j<=ylen;j++) //首列清0
arr[j][0].first=0;
for ( int i=1;i<=ylen;i++){
char s=str2.at(i-1);
for ( int j=1;j<=xlen;j++){
int leftup=arr[i-1][j-1].first;
int left=arr[i][j-1].first;
int up=arr[i-1][j].first;
if (str1.at(j-1)==s) //C1==C2
leftup++;
int max;
arr[i][j].second=Max(leftup,left,up,&arr[i][j].first);
// cout<<arr[i][j].first<<arr[i][j].second<<" ";
}
// cout<<endl;
} /*矩阵构造完毕*/
//回溯找出最长公共子序列
stack< int > st;
int i=ylen,j=xlen;
while (!(i==0&&j==0)){
if (arr[i][j].second==LEFTUP){
if (arr[i][j].first==arr[i-1][j-1].first+1)
st.push(i);
--i;
--j;
}
else if (arr[i][j].second==LEFT){
--j;
}
else if (arr[i][j].second==UP){
--i;
}
}
string res= "" ;
while (!st.empty()){
int index=st.top()-1;
res.append(str2.substr(index,1));
st.pop();
}
return res;
}
int main(){
string str1= "GCCCTAGCG" ;
string str2= "GCGCAATG" ;
string lcs=LCS(str1,str2);
cout<<lcs<<endl;
return 0;
}
|
下面给一个Java版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public static <E> List<E> longestCommonSubsequence(E[] s1,E[] s2){
int [][] num= new int [s1.length+ 1 ][s2.length+ 1 ];
for ( int i= 1 ;i<s1.length;i++){
for ( int j= 1 ;j<s2.length;j++){
if (s1[i- 1 ].equals(s2[j- 1 ])){
num[i][j]= 1 +num[i- 1 ][j- 1 ];
}
else {
num[i][j]=Math.max(num[i- 1 ][j],num[i][j- 1 ]);
}
}
}
System.out.println( "lenght of LCS= " +num[s1.length][s2.length]);
int s1position=s1.length,s2position=s2.length;
List<E> result= new LinkedList<E>();
while (s1position!= 0 && s2position!= 0 ){
if (s1[s1position- 1 ].equals(s2[s2position- 1 ])){
result.add(s1[s1position- 1 ]);
s1position--;
s2position--;
}
else if (num[s1position][s2position- 1 ]>=num[s1position- 1 ][s2position]){
s2position--;
}
else {
s1position--;
}
}
Collections.reverse(result);
return result;
}
|
std::endl是一个特殊值,称为操纵符(manipulator),将它写入输出流时具有输出换行的效果,并刷新与设备相关联的缓冲区(buffer)。通过刷新缓冲区用户可立即看到写入到流中的输出。
我在调试以上代码时在45行(cout<<endl;)处设置断点,结果发现“43行(cout<<arr[i][j].first<<arr[i][j].second<<" ";) 没有执行”,这就是因为43行末尾没有加endl,所以用户没有立即看到输出流中的数据。
字符串编辑距离
要想把字符串S1变成S2,可以经过若干次下列原子操作:
1.删除一个字符
2.增加一个字符
3.更改一个字符
字符串S1和S2的编辑距离定义为从S1变成S2所需要原子操作的最少次数。
解法跟上面的最长公共子序列十分相似,都是动态规划,把一个问题转换为若干个规模更小的子问题,并且都借助于一个二维矩阵来实现计算。
约定:字符串S去掉最后一个字符T后为S',T1和T2分别是S1和S2的最后一个字符。
则dist(S1,S2)是下列4个值的最小者:
1.dist(S1',S2')--当T1==T2
2.1+dist(S1',S2)--当T1!=T2,并且删除S1的最后一个字符T1
3.1+dist(S1,S2')--当T1!=T2,并且在S1后面增加一个字符T2
4.1+dist(S1',S2')--当T1!=T2,并且把S1的最的一个字符T1改成T2
把问题转换为二维矩阵:
arr[i][j]表示S1.sub(0,i)和S2.sub(0,j)的编辑距离,则
arr[i][j]=min{1+arr[i][j-1], 1+arr[i-1][j], 1+arr[i-1][j-1](当S1[i]!=S2[j]), arr[i-1][j-1](当S1[i]==S2[j])}
边界情况:arr[0][j]=j, arr[i][0]=i
代码就不写了,跟最长公共子序列很相似。
计算两个字条串的相似度除了Edit Distance,还有一种方法是计算Jaro Distance。具体怎么算读者可以搜一下。
序列试题---最大子序列、最长递增子序列、最长公共子串、最长公共子序列、字符串编辑距离 .相关推荐
- Algorithm:C++/python语言实现之求旋转数组最小值、求零子数组、求最长公共子序列和最长公共子串、求LCS与字符串编辑距离
Algorithm:C++/python语言实现之求旋转数组最小值.求零子数组.求最长公共子序列和最长公共子串.求LCS与字符串编辑距离 目录 一.求旋转数组最小值 1.分析问题 2.解决思路 二.求 ...
- 最长公共子序列|最长公共子串|最长重复子串|最长不重复子串|最长回文子串|最长递增子序列|最大子数组和...
最长公共子序列|最长公共子串|最长重复子串|最长不重复子串|最长回文子串|最长递增子序列|最大子数组和 文章作者:Yx.Ac 文章来源:勇幸|Thinking (http://www.ahathi ...
- 动态规划:最长公共子串 最长公共子序列
一.最长公共子串 1. 题目 给定两个序列 X 和 Y,如果 Z 即是 X 的子串,又是 Y 的子串,我们就称它是 X 和 Y 的公共子串,注意子串是连续的. 例如 X = { A, B, C, D, ...
- 【To Understand】动态规划:求最长公共子串/最长公共子序列
动态规划:求最长公共子串/最长公共子序列 本博客转载自:https://blog.csdn.net/u013074465/article/details/45392687 该博客中详细讲解了求最长公共 ...
- 第六周作业(等值字串,KMP匹配,大整数相乘,最长公共子串,判断两个字符串是否匹配,最长回文子串,年号字串)
目录 1.等值字串 2.KMP匹配 3.大整数相乘 4.最长公共子串 5.判断两个字符串是否匹配 6.最长回文字串 7.年号字串 补发一下,原来忘记发了. 1.等值字串 [问题描述]如果字符串的一个子 ...
- python求最长公共子串_Python-求解两个字符串的最长公共子序列
一.问题描述 给定两个字符串,求解这两个字符串的最长公共子序列(Longest Common Sequence).比如字符串1:BDCABA:字符串2:ABCBDAB.则这两个字符串的最长公共子序列长 ...
- 数组字符串那些经典算法:最大子序列和,最长递增子序列,最长公共子串,最长公共子序列,字符串编辑距离,最长不重复子串,最长回文子串 (转)...
作者:寒小阳 时间:2013年9月. 出处:http://blog.csdn.net/han_xiaoyang/article/details/11969497. 声明:版权所有,转载请注明出处,谢谢 ...
- 【恋上数据结构】动态规划(找零钱、最大连续子序列和、最长上升子序列、最长公共子序列、最长公共子串、0-1背包)
动态规划(Dynamic Programming) 练习1:找零钱 找零钱 - 暴力递归 找零钱 - 记忆化搜索 找零钱 - 递推 思考题:输出找零钱的具体方案(具体是用了哪些面值的硬币) 找零钱 - ...
- 【qduoj - 夏季学期创新题】最长公共子串(水题暴力枚举,不是LCS啊)
题干: 描述 编写一个程序,求两个字符串的最长公共子串.输出两个字符串的长度,输出他们的最长公共子串及子串长度.如果有多个最长公共子串请输出在第一个字符串中先出现的那一个. 特别注意公共子串中可能包含 ...
- 【qduoj】最长公共子串
题干: 描述 编写一个程序,求两个字符串的最长公共子串.输出两个字符串的长度,输出他们的最长公共子串及子串长度.如果有多个最长公共子串请输出在第一个字符串中先出现的那一个. 特别注意公共子串中可能包含 ...
最新文章
- java.library.path hadoop_java - Hadoop“无法为您的平台加载native-hadoop库”警告
- linux虚拟内存 ppt,Linux虚拟内存管理基础v2研究报告.ppt
- 搞定所有的跨域请求问题
- python成员运算符包括_Python 使用成员运算符案例
- 金蝶kiss对计算机软件的要求,金蝶软件的产品适用范围分析
- 代码管理工具之git的学习
- 如何添加天锐绿盾屏幕水印
- 厉害了!图解 23 种设计模式,终于有人讲清楚了!面试揭秘
- 概率图模型_马尔可夫随机场
- 【计算理论】正则语言 ( 正则语言运算 | 正则语言封闭性 )
- jupyter notebook 配置没问题却连接不上服务器
- 服装行业如何做软文营销推广产品?
- 迷宫问题的求解(广度和深度优先搜索)
- [python]遍历字典dict的几种方法
- NSGA-Ⅲ算法的基本原理
- 哪款蓝牙耳机的音质好?盘点四款高颜值蓝牙耳机
- Java设置中文字体大小_eclipse中文字体大小修改,让中英文字体协调
- Mysql中使用Update From语句
- QQ2011 Beta3优先体验(附官方体验地址及下载地址)
- 【mba旅游管理论文】天乐湖旅游度假区发展现状及问题分析(节选)
热门文章
- android怎么用apk,手机如何安装APK应用教程 小白操作安卓系统教程 (全文)
- go sum: verifying module: checksum mismatch
- NOIP2017 PJ蒟蒻的游记
- 局域网之间两台计算机同屏,spacedesk解决两台电脑同屏的问题
- Java类和对象(未完待续,持续更新)
- WEB入门.九 导航菜单
- Keil5中提示warning C318: can‘t open file ‘xxx.h‘
- Android开启OTG功能/USB Host API功能
- 《十二生肖运程图》网站欣赏
- 计算机多媒体专业就业现状,计算机多媒体技术就业前景怎么样