最长字串及子序列问题

来源:互联网 发布:js json操作 编辑:程序博客网 时间:2024/05/22 02:21

这类问题属于noip常用类型,一般有以下几种情况

最长不降(不升)子序列

最长上升(下降)子序列

最长公共子序列

最长公共字串(LCS)

最长上升公共子序列


一、最长不降(不升)子序列

①朴素的O(N*N);

②program nlogn;//最长不下降子序列//其实与下降子序列相比,只是多了可能有重复的数字

var
d,a:array[0..100000] of longint;
len,i,k,n:longint;
Function min(t:longint):longint;
var
s,w,mid:longint;
begin
s:=0;w:=len;
while s<w do
begin
mid:=(s+w)div 2;
if d[mid]<=t then s:=mid+1 else w:=mid;
end; min:=s;
end;
begin
readln(n);
for i:=1 to n do read(a[i]);
readln;
len:=0;
d[0]:=-maxlongint;
for i:=1 to n do begin
if a[i]>=d[len] then begin
inc(len);
d[len]:=a[i];
end else begin
k:=min(a[i]);
d[k]:=a[i];
end;
end;
writeln(len);
end.//二分

二、最长上升子序列
  var n,i,l,r,x,mid:longint;  
        f:array[0..10000]of longint;
   begin  
  read(n);  
   for i:=1 to n do  
   begin 
    read(x); 
l:=1;
      r:=f[0];    
   while l<r do   
      begin 
         mid:=(l+r)shr 1;
      if f[mid]<x then //若求最长下降子序列则是f[mid]>x 
      l:=mid+1 
            else r:=mid;  
      end;   
  if (l>=r)and(x>f[f[0]]))then //若求最长下降子序列则是x<f[f[0]]       
  begin       
    f[0]:=f[0]+1;      
    f[f[0]]:=x;         
      l:=f[0];     
    end 
    else      
  if x<f[l] then //若求最长下降子序列则是x>f[l]      
       f[l]:=x;   
   end; 
 write(f[0]); 
end.

三、最长公共子序列

O(N*N)?

O(N logn)?

其实就是离散+最长上升序列

四、最长公共子串

最优的复杂度一般为O(N*N)

① 构矩阵?

摘录:动态规划有一个经典问题是最长公共子序列,但是这里的子序列不要求连续,如果要求序列是连续的,我们叫公共子串,那应该如何得到这个串呢?最简单的方法就是依次比较,以某个串为母串,然后生成另一个串的所有长度的子串,依次去母串中比较查找,这里可以采用先从最长的子串开始,减少比较次数,但是复杂度依然很高!然后重新看一下这个问题,我们建立一个比较矩阵来比较两个字符串str1和str2定义 lcs(i,j) ,当str1[i] = str2[j]时lcs(i,j)=1,否则等于0。example:str1 = "bab"str2 = "caba"建立矩阵--b a bc 0 0 0a 0 1 0b 1 0 1a 0 1 0连续i子串的特点就是如果str1[i]和str2[j]是属于某公共子串的最后一个字符,那么一定有str1[i]=str2[j] && str1[i-1] = str2[j-1],从矩阵中直观的看,就是由“1”构成的“斜线”代表的序列都是公共子串,那么最长公共子串肯定就是斜线“1”最长的那个串。那么现在问题就可以转化了,只要构造出如上的一个矩阵,用n^2的时间就可以得到矩阵,然后再到矩阵中去寻找最长的那个“1”构成的斜线就可以了!那么,现在又有了新的问题?如何快速的找到那个“1”构成的最长斜线呢?采用DP的思想,如果str1[i] = str2[j],那么此处的包含str1[i] 和 str2[j]公共子串的长度必然是包含str1[i-1]和str2[j-1]的公共子串的长度加1,那么现在我们可以重新定义lcs(i,j),即是lcs(i,j) = lcs(i-1,j-1) + 1,反之,lcs(i,j) = 0。那么上面的矩阵就变成了如下的样子:--b a bc 0 0 0a 0 1 0b 1 0 2a 0 2 0现在问题又变简单了,只需要花n^2的时间构造这样一个矩阵,再花n^2的时间去找到矩阵中最大的那个值,对应的就是最长公共子串的长度,而最大值对应的位置对应的字符,就是最长公共子串的最末字符。算法还可以改进,我们可以将查找最大长度和对应字符的工作放在构造矩阵的过程中完成,一边构造一边记录当前的最大长度和对应位置,这样就节省了n^2的查找时间。空间上也可以做改进,如果按照如上的方式构造,我们发现,当矩阵的第i+1行的值计算完成后,第i行的值就没有用了,即便是最长的长度出现在第i行,我们也已经用变量记录下来了。因此,可以将矩阵缩减成一个向量来处理,向量的当前值对应第i行,向量的下一个循环后的值对应第i+1行。

②定义二元函数函数f(m,n):分别以str1[m],str2[n]结尾的连续公共子串的长度
而对于f(m+1,n+1) 有以下两种情况
1.str1[m+1] != str2[n+1],则有f(m+1,n+1) =0
2.str1[m+1] == str2[n+1],则有f(m+1,n+1) = f(m,n) + 1
另外f(0,j) = 0(j>=0)
  f(j,0) = 0 (j>=0)
按照上面这个公式,我们用容易写出这个算法的实现
算法实现
   int commstr(char *str1, char *str2)
/* 返回str1,str2的最长公共之串长度*/
 {
       int len1=strlen(str1),len2=strlen(str2),row,col,max=0;
    int **pf = new int*[len1+1];//动态分配一个二维数组作为辅助空间
       for (row=0; row<len1+1; row++)
      pf[row] = new int[len2+1];    //数组赋初值
 for (row=0; row<len1+1; row++)
      pf[row][0] = 0;

for (col=0; col<len2+1; col++)

pf[0][col] = 0;

    for (col=1;col<=len2; col++)
{
   if (str1[row-1] == str2[col-1])
  {
   pf[row][col] = pf[row-1][col-1] + 1;
 max = pf[row][col] > max ? pf[row][col] : max;
 }
  else
 pf[row][col] = 0;
  }//空间回收     
 for (row=0; row<len1+1; row++)
delete[] pf[row];
  delete[] pf;
 return max;                   

五、最长上升公共子序列

引用Matrix67的话   
这里要说的这个算法利用了nlogn的最长上升子序列(LIS)的技巧:用f[k]表示长度为k的上升子序列最后一个数最小是多少。
在最长公共上升子序列中,令f[i,j][k]表示A串前i个数字,B串前j个数字,长度为k的公共上升子序列中,最后一个数最小是多少。
 当A[i]=B[j]时,像nlogn的最长上升子序列一样把A[i]插入到f[i-1,j]中,这需要线性的时间扫一遍f[i,j];
 当A[i]<>B[j]时,我们需要合并f[i-1,j]和f[i,j-1],使得对于每个k满足f[i,j][k]:=min{ f[i-1,j][k],f[i,j-1][k] }。这需要线性的时间扫一边f[i-1,j]和f[i,j-1]并取k相同时的较小值。
  最后输出f[n,m]的长度(使f[n,m][k]有意义的最大的k)。
  这样的复杂度是三方的,我们需要优化。
 考虑A[i]=B[j]的情况。当i固定时,随着j的增加,插入的位置一定也在后移,因为同样是插入的A[i],但j的增加(B串长度的增加)使得f [i,j]更优,因此可以更新的值就更靠后。于是,对于每个i,我们可以按照k的顺序扫描f[i-1,j][k] 并在A[i]可以插入f[i-1][j]的k位置时增加j,从而预处理所有A[i]=B[j]时A[i]应该插入的位置。
再考虑A[i]<>B[j]的情况。从定义看,f[i-1,j-1]和f[i-1,j]只有一个地方不一样,因为多一个数最多只能造成一个k 的值变小;同样地,f[i-1,j-1]和f[i,j-1]也只有一个地方不一样。因此,f[i-1,j]和f[i,j-1]最多只有两个k所对应的值不相同,且当有两个不同的值时,总是f[i-1,j]中的某个值较小,f[i,j-1]中的某个值较小。这给我们优化的余地。在每次处理完f[i,j]时,我们可以记录一个值x[i,j]表示f[i,j][k]与f[i-1,j][k]中值不一样的k是多少,在A[i]=B[j]时直接赋值为插入的位置,在 A[i]<>B[j]时待后文说明。以后合并时,先让f[i,j]:=f[i-1,j](由于此时的f[i-1,j]已经没有别的用处了,因此可以用滚动数组记录,直接令f[i-1,j]是f[i,j],避免实际的赋值操作),然后将新的f[i,j]中的,使f[i,j-1][k]比f[i- 1, j][k]小的k所对应值更新。这个k是多少呢?显然应该是x[i,j-1]。这样的操作同时可以确定x[i,j]=x[i,j-1]。
    这样,复杂度就达到了平方。

0 0