給定兩個字符串,求解這兩個字符串的最長公共子序列(Longest Common Sequence)。好比字符串1:BDCABA;字符串2:ABCBDAB。則這兩個字符串的最長公共子序列長度爲4,最長公共子序列是:BCBApython
這是一個動態規劃的題目。對於可用動態規劃求解的問題,通常有兩個特徵:①最優子結構;②重疊子問題算法
①最優子結構優化
設X=(x1,x2,...,xn)和Y=(y1,y2,...,ym)是兩個序列,將X和Y的最長公共子序列記爲LCS(X,Y)spa
找出LCS(X,Y)就是一個最優化問題。由於,咱們須要找到X和Y中最長的那個公共子序列。而要找X和Y的LCS,首先考慮X的最後一個元素和Y的最後一個元素。3d
⑴若是xn=ym,即X的最後一個元素與Y的最後一個元素相同,這說明該元素必定位於公共子序列中。所以,如今只須要找:LCS(Xn-1,Ym-1)code
LCS(Xn-1,Ym-1)就是原問題的一個子問題。爲何叫子問題?由於它的規模比原問題小。blog
爲何是最優的子問題?由於咱們要找的是Xn-1和Ym-1的最長公共子序列啊。最長的!換句話說就是最優的那個。遞歸
⑵若是xn!=ym,這下要麻煩一點,由於它產生了兩個子問題:LCS(Xn-1,Ym)和LCS(Xn,Ym-1)utf-8
由於序列X和序列Y的最後一個元素不相等,那說明最後一個元素不多是最長公共子序列中的元素。字符串
LCS(Xn-1,Ym)表示:最長公共序列能夠在(x1,x2,...xn-1)和(y1,y2,...,ym)中找。
LCS(Xn,Ym-1)表示:最長公共序列能夠在(x1,x2,...xn)和(y1,y2,...,ym-1)中找。
求解上面兩個子問題,獲得的公共子序列誰最長,那誰就是LCS(X,Y)。用數學表示就是:
LCS=max{LCS(Xn-1,Ym),LCS(Xn,Ym-1)}
因爲條件⑴和⑵考慮到了全部可能的狀況。所以,咱們成功的把原問題轉化成了三個規模更小的問題。
②重疊子問題
重疊子問題是什麼?就是說原問題轉化成子問題後,子問題中有相同的問題。
原問題是:LCS(X,Y)。子問題有❶LCS(Xn-1,Ym-1)❷ LCS(Xn-1,Ym)❸ LCS(Xn,Ym-1)
乍一看,這三個問題是不重疊的。可本質上它們是重疊的,由於它們只重疊了一大部分。舉例:
第二個子問題:LCS(Xn-1,Ym)就包含了問題❶LCS(Xn-1,Ym-1),爲何?
由於,當Xn-1和Ym的最後一個元素不相同時,咱們又須要將LCS(Xn-1,Ym-1)進行分解:分解成:LCS(Xn-1,Ym-1)和LCS(Xn-2,Ym)
也就是說:在子問題的繼續分解中,有些問題是重疊的。
因爲像LCS這樣的問題,它具備重疊子問題的性質,所以:用遞歸來求解就太不划算了。國爲採用遞歸,它重複地求解了子問題,並且須要注意的是,全部子問題加起來的個數是指數級的。
那麼問題來了,若是用遞歸求解,有指數級個子問題,故時間複雜度是指數級的。這指數級個子問題,難道用了動態規劃,就變成多項式時間了??
關鍵是採用動態規劃時,並不須要去一一計算那些重疊了的子問題。或者說:用了動態規劃以後,有些子問題是經過「查表」直接獲得的,而不是從新又計算一遍獲得的。舉個例子:好比求Fib數列。
求fib(5),分解成了兩個子問題:fib(4)和fib(3),求解fib(4)和fib(3)時,又分解了一系列的小問題...
從圖中能夠看出:根的左右子樹:fib(4)和fib(3)下,是有不少重疊的!好比,對於fib(2),它就一共出現了三次。若是用遞歸來求解,fib(2)就會被計算三次,而用DP(Dynamic Programming)動態規劃,則fib(2)只會計算一次,其餘兩次則是經過「查表」直接求得。並且,更關鍵的是:查找求得該問題的解以後,就不須要再繼續去分解該問題了。而對於遞歸,是不斷地將問題解,直到分解爲基準問題(fib(0)或者fib(1))
說了這麼多,仍是寫下最長公共子序列的遞歸式才完整。
C[i,j]表示:(x1,x2,...,xi)和(y1,y2,...,yj)的最長公共子序列的長度。公式的具體解釋可參考《算法導論》動態規劃章節
#! /usr/bin/env python3 # -*- coding:utf-8 -*- # Author : mayi # Blog : http://www.cnblogs.com/mayi0312/ # Date : 2019/5/16 # Name : test03 # Software : PyCharm # Note : 用於實現求解兩個字符串的最長公共子序列 def longestCommonSequence(str_one, str_two, case_sensitive=True): """ str_one 和 str_two 的最長公共子序列 :param str_one: 字符串1 :param str_two: 字符串2(正確結果) :param case_sensitive: 比較時是否區分大小寫,默認區分大小寫 :return: 最長公共子序列的長度 """ len_str1 = len(str_one) len_str2 = len(str_two) # 定義一個列表來保存最長公共子序列的長度,並初始化 record = [[0 for i in range(len_str2 + 1)] for j in range(len_str1 + 1)] for i in range(len_str1): for j in range(len_str2): if str_one[i] == str_two[j]: record[i + 1][j + 1] = record[i][j] + 1 elif record[i + 1][j] > record[i][j + 1]: record[i + 1][j + 1] = record[i + 1][j] else: record[i + 1][j + 1] = record[i][j + 1] return record[-1][-1] if __name__ == '__main__': # 字符串1 s1 = "BDCABA" # 字符串2 s2 = "ABCBDAB" # 計算最長公共子序列的長度 res = longestCommonSequence(s1, s2) # 打印結果 print(res) # 4