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

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

                       题目:如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,则字符串一称之为字符串二的子串。注意,并不要求子串(字符串一)的字符必须连续出现在字符串二中。请编写一个函数,输入两个字符串,求它们的最长公共子序列,并打印出最长公共子序列。
      例如:输入两个字符串BDCABA和ABCBDAB,字符串BCBA和BDAB都是是它们的最长公共子序列,则输出它们的长度4,并打印任意一个子序列。

       分析:求最长公共子序列(Longest Common Subsequence, LCS)是一道非常经典的动态规划题,因此一些重视算法的公司像MicroStrategy都把它当作面试题。

       完整介绍动态规划将需要很长的篇幅,因此我不打算在此全面讨论动态规划相关的概念,只集中对LCS直接相关内容作讨论。如果对动态规划不是很熟悉,请参考相关算法书比如算法讨论。

       考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am-1”,B=“b0,b1,…,bn-1”,并Z=“z0,z1,…,zk-1”为它们的最长公共子序列。不难证明有以下性质:

(1) 如果am-1==bn-1,则zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…,am-2”和“b0,b1,…,bn-2”的一个最长公共子序列;

(2) 如果am-1!=bn-1,则若zk-1!=am-1时,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列;

(3) 如果am-1!=bn-1,则若zk-1!=bn-1时,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列。

      这样,在找A和B的公共子序列时,如果有am-1==bn-1,则进一步解决一个子问题,找“a0,a1,…,am-2”和“b0,b1,…,bm-2”的一个最长公共子序列;如果am-1!=bn-1,则要解决两个子问题,找出“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列和找出“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列,再取两者中较长者作为A和B的最长公共子序列。

       求解:
       引进一个二维数组c[][],用c[i][j]记录X[i]与Y[j] 的LCS 的长度,b[i][j]记录c[i][j]是通过哪一个子问题的值求得的,以决定输出最长公共字串时搜索的方向。
      我们是自底向上进行递推计算,那么在计算c[i,j]之前,c[i-1][j-1],c[i-1][j]与c[i][j-1]均已计算出来。此时我们根据X[i] == Y[j]还是X[i] != Y[j],就可以计算出c[i][j]。

      问题的递归式写成:

      回溯输出最长公共子序列过程:

 

       算法分析:
      由于每次调用至少向上或向左(或向上向左同时)移动一步,故最多调用(m + n)次就会遇到i = 0或j = 0的情况,此时开始返回。返回时与递归调用时方向相反,步数相同,故算法时间复杂度为Θ(m + n)。

      完整的实现代码如下:

/** 找出两个字符串的最长公共子序列的长度** author :liuzhiwei  ** data   :2011-08-15**/ #include "stdio.h"#include "string.h"#include "stdlib.h"int LCSLength(char* str1, char* str2, int **b)int i,j,length1,length2,len; length1 = strlen(str1); length2 = strlen(str2); //双指针的方法申请动态二维数组 int **c = new int*[length1+1];      //共有length1+1行 for(i = 0; i < length1+1; i++)  c[i] = new int[length2+1];      //共有length2+1列 for(i = 0; i < length1+1; i++)  c[i][0]=0;        //第0列都初始化为0 for(j = 0; j < length2+1; j++)  c[0][j]=0;        //第0行都初始化为0 for(i = 1; i < length1+1; i++) {  for(j = 1; j < length2+1; j++)  {   if(str1[i-1]==str2[j-1])   //由于c[][]的0行0列没有使用,c[][]的第i行元素对应str1的第i-1个元素   {    c[i][j]=c[i-1][j-1]+1;    b[i][j]=0;          //输出公共子串时的搜索方向   }   else if(c[i-1][j]>c[i][j-1])   {    c[i][j]=c[i-1][j];    b[i][j]=1;   }   else   {    c[i][j]=c[i][j-1];    b[i][j]=-1;   }  } } /* for(i= 0; i < length1+1; i++) { for(j = 0; j < length2+1; j++) printf("%d ",c[i][j]); printf("\n"); } */ len=c[length1][length2]; for(i = 0; i < length1+1; i++)    //释放动态申请的二维数组  delete[] c[i]; delete[] c; return len;}void PrintLCS(int **b, char *str1, int i, int j)if(i==0 || j==0)  returnif(b[i][j]==0) {  PrintLCS(b, str1, i-1, j-1);   //从后面开始递归,所以要先递归到子串的前面,然后从前往后开始输出子串  printf("%c",str1[i-1]);        //c[][]的第i行元素对应str1的第i-1个元素 } else if(b[i][j]==1)  PrintLCS(b, str1, i-1, j); else  PrintLCS(b, str1, i, j-1);}int main(void)char str1[100],str2[100]; int i,length1,length2,len; printf("请输入第一个字符串:"); gets(str1); printf("请输入第二个字符串:"); gets(str2); length1 = strlen(str1); length2 = strlen(str2); //双指针的方法申请动态二维数组 int **b = new int*[length1+1]; for(i= 0; i < length1+1; i++)  b[i] = new int[length2+1]; len=LCSLength(str1,str2,b); printf("最长公共子序列的长度为:%d\n",len); printf("最长公共子序列为:"); PrintLCS(b,str1,length1,length2); printf("\n"); for(i = 0; i < length1+1; i++)    //释放动态申请的二维数组  delete[] b[i]; delete[] b; system("pause"); return 0;}

          程序的效果图如下:


      第二种方法为:

/** 找出两个字符串的最长公共子序列的长度** author :liuzhiwei  ** data   :2011-08-15**/ #include "stdio.h"#include "string.h"#include "stdlib.h"int LCSLength(char* str1, char* str2)    //求得两个字符串的最大公共子串长度并输出公共子串int i,j,length1,length2; length1 = strlen(str1); length2 = strlen(str2); //双指针的方法申请动态二维数组 int **c = new int*[length1+1];      //共有length1+1行 for(i = 0; i < length1+1; i++)  c[i] = new int[length2+1];      //共有length2+1列 for(i = 0; i < length1+1; i++)  c[i][0]=0;        //第0列都初始化为0 for(j = 0; j < length2+1; j++)  c[0][j]=0;        //第0行都初始化为0 for(i = 1; i < length1+1; i++) {  for(j = 1; j < length2+1; j++)  {   if(str1[i-1]==str2[j-1])   //由于c[][]的0行0列没有使用,c[][]的第i行元素对应str1的第i-1个元素    c[i][j]=c[i-1][j-1]+1;   else if(c[i-1][j]>c[i][j-1])    c[i][j]=c[i-1][j];   else    c[i][j]=c[i][j-1];  } } //输出公共子串 char s[100]; int len,k; len=k=c[length1][length2]; s[k--]='\0'; i=length1,j=length2; while(i>0 && j>0) {  if(str1[i-1]==str2[j-1])  {   s[k--]=str1[i-1];   i--;   j--;  }  else if(c[i-1][j]<c[i][j-1])   j--;  else   i--; } printf("最长公共子串为:"); puts(s); for(i = 0; i < length1+1; i++)    //释放动态申请的二维数组  delete[] c[i]; delete[] c; return len;}int main(void)char str1[100],str2[100]; int length1,length2,len; printf("请输入第一个字符串:"); gets(str1); printf("请输入第二个字符串:"); gets(str2); length1 = strlen(str1); length2 = strlen(str2); len=LCSLength(str1,str2); printf("最长公共子串的长度为:%d\n",len); system("pause"); return 0;}

       问题拓展:设A、B、C是三个长为n的字符串,它们取自同一常数大小的字母表。设计一个找出三个串的最长公共子序列的O(n^3)的时间算法。
       思路:跟上面的求2个字符串的公共子序列是一样的思路,只不过这里需要动态申请一个三维的数组,三个字符串的尾字符不同的时候,考虑的情况多一些而已。

/** 找出三个字符串的最长公共子序列的长度** author :liuzhiwei  ** data   :2011-08-15**/ #include "stdio.h"#include "string.h"#include "stdlib.h"int max1(int m,int n)if(m>n)  return m; else  return n;}int max2(int x,int y,int z,int k,int m,int n)int max=-1if(x>max)  max=x; if(y>max)  max=y; if(z>max)  max=z; if(k>max)  max=k; if(m>max)  max=m; if(n>max)  max=n; return max;}int LCSLength(char* str1, char* str2, char* str3)    //求得三个字符串的最大公共子序列长度并输出公共子序列int i,j,k,length1,length2,length3,len; length1 = strlen(str1); length2 = strlen(str2); length3 = strlen(str3); //申请动态三维数组 int ***c = new int**[length1+1];      //共有length1+1行 for(i = 0; i < length1+1; i++) {  c[i] = new int*[length2+1];      //共有length2+1列  for(j = 0; j<length2+1; j++)   c[i][j] = new int[length3+1]; } for(i = 0; i < length1+1; i++) {  for(j = 0; j < length2+1; j++)   c[i][j][0]=0; } for(i = 0; i < length2+1; i++) {  for(j = 0; j < length3+1; j++)   c[0][i][j]=0; } for(i = 0; i < length1+1; i++) {  for(j = 0; j < length3+1; j++)   c[i][0][j]=0;    } for(i = 1; i < length1+1; i++) {  for(j = 1; j < length2+1; j++)  {   for(k = 1; k < length3+1; k++)   {    if(str1[i-1]==str2[j-1] && str2[j-1]==str3[k-1])     c[i][j][k]=c[i-1][j-1][k-1]+1;    else if(str1[i-1]==str2[j-1] && str1[i-1]!=str3[k-1])     c[i][j][k]=max1(c[i][j][k-1],c[i-1][j-1][k]);    else if(str1[i-1]==str3[k-1] && str1[i-1]!=str2[j-1])     c[i][j][k]=max1(c[i][j-1][k],c[i-1][j][k-1]);    else if(str2[j-1]==str3[k-1] && str1[i-1]!=str2[j-1])     c[i][j][k]=max1(c[i-1][j][k],c[i][j-1][k-1]);    else    {     c[i][j][k]=max2(c[i-1][j][k],c[i][j-1][k],c[i][j][k-1],c[i-1][j-1][k],c[i-1][j][k-1],c[i][j-1][k-1]);    }   }  } } len=c[length1][length2][length3]; for(i = 1; i < length1+1; i++)          //释放动态申请的三维数组 {  for(j = 1; j < length2+1; j++)   delete[] c[i][j];  delete[] c[i]; } delete[] c; return len;}int main(void)char str1[100],str2[100],str3[100]; int len; printf("请输入第一个字符串:"); gets(str1); printf("请输入第二个字符串:"); gets(str2); printf("请输入第三个字符串:"); gets(str3); len=LCSLength(str1,str2,str3); printf("最长公共子序列的长度为:%d\n",len); system("pause"); return 0;}

        程序的效果图如下:

           

给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow
这里写图片描述
你好! 这是你第一次使用 **Markdown编辑器** 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

新的改变

我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

  1. 全新的界面设计 ,将会带来全新的写作体验;
  2. 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
  3. 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
  4. 全新的 KaTeX数学公式 语法;
  5. 增加了支持甘特图的mermaid语法1 功能;
  6. 增加了 多屏幕编辑 Markdown文章功能;
  7. 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
  8. 增加了 检查列表 功能。

功能快捷键

撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G

合理的创建标题,有助于目录的生成

直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

如何改变文本的样式

强调文本 强调文本

加粗文本 加粗文本

标记文本

删除文本

引用文本

H2O is是液体。

210 运算结果是 1024.

插入链接与图片

链接: link.

图片: Alt

带尺寸的图片: Alt

当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

如何插入一段漂亮的代码片

博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

// An highlighted block var foo = 'bar'; 

生成一个适合你的列表

  • 项目
    • 项目
      • 项目
  1. 项目1
  2. 项目2
  3. 项目3
  • 计划任务
  • 完成任务

创建一个表格

一个简单的表格是这么创建的:

项目Value
电脑$1600
手机$12
导管$1

设定内容居中、居左、居右

使用:---------:居中
使用:----------居左
使用----------:居右

第一列第二列第三列
第一列文本居中第二列文本居右第三列文本居左

SmartyPants

SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:

TYPEASCIIHTML
Single backticks'Isn't this fun?'‘Isn’t this fun?’
Quotes"Isn't this fun?"“Isn’t this fun?”
Dashes-- is en-dash, --- is em-dash– is en-dash, — is em-dash

创建一个自定义列表

Markdown
Text-to- HTML conversion tool
Authors
John
Luke

如何创建一个注脚

一个具有注脚的文本。2

注释也是必不可少的

Markdown将文本转换为 HTML

KaTeX数学公式

您可以使用渲染LaTeX数学表达式 KaTeX:

Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n1)!nN 是通过欧拉积分

Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t &ThinSpace; . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=0tz1etdt.

你可以找到更多关于的信息 LaTeX 数学表达式here.

新的甘特图功能,丰富你的文章

gantt
        dateFormat  YYYY-MM-DD
        title Adding GANTT diagram functionality to mermaid
        section 现有任务
        已完成               :done,    des1, 2014-01-06,2014-01-08
        进行中               :active,  des2, 2014-01-09, 3d
        计划一               :         des3, after des2, 5d
        计划二               :         des4, after des3, 5d
  • 关于 甘特图 语法,参考 这儿,

UML 图表

可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图::

张三 李四 王五 你好!李四, 最近怎么样? 你最近怎么样,王五? 我很好,谢谢! 我很好,谢谢! 李四想了很长时间, 文字太长了 不适合放在一行. 打量着王五... 很好... 王五, 你怎么样? 张三 李四 王五

这将产生一个流程图。:

链接
长方形
圆角长方形
菱形
  • 关于 Mermaid 语法,参考 这儿,

FLowchart流程图

我们依旧会支持flowchart的流程图:

  • 关于 Flowchart流程图 语法,参考 这儿.

导出与导入

导出

如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

导入

如果你想加载一篇你写过的.md文件或者.html文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。


  1. mermaid语法说明 ↩︎

  2. 注脚的解释 ↩︎

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值