1. 程式人生 > >程式設計師面試100題之六 最長公共子序列

程式設計師面試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實體。例如:

TYPE ASCII HTML
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 是通過尤拉積分

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

你可以找到更多關於的資訊 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. 註腳的解釋 ↩︎