1. 程式人生 > >演算法55----最長子序列

演算法55----最長子序列

一、題目:最長公共子序列:

給定兩個字串,求解這兩個字串的最長公共子序列(Longest Common Sequence)。比如字串L:BDCABA;字串S:ABCBDAB

則這兩個字串的最長公共子序列長度為4,最長公共子序列是:BCBA

思路:動態規劃:時間O(n * m),空間O(n * m)

建立 DP陣列C[i][j]:表示子字串L【:i】和子字串S【:j】的最長公共子序列個數。

狀態方程:

個數程式碼:

def LCS(L,S):
    if not L or not S:
        return ""
    dp = [[0] * (len(L)+1) for
i in range(len(S)+1)] for i in range(len(S)+1): for j in range(len(L)+1): if i == 0 or j == 0: dp[i][j] = 0 else: if L[j-1] == S[i-1]: dp[i][j] = dp[i-1][j-1] + 1 else: dp[i][j]
= max(dp[i-1][j],dp[i][j-1]) return dp[-1][-1] L = 'BDCABA' S = 'ABCBDAB' LCS(L,S)

最長子序列程式碼:設定一個標誌

def LCS(L,S):
    if not L or not S:
        return ""
    res = ''
    dp = [[0] * (len(L)+1) for i in range(len(S)+1)]
    flag = [['left'] * (len(L)+1) for i in range(len(S)+1)]
    for i in
range(len(S)+1): for j in range(len(L)+1): if i == 0 or j == 0: dp[i][j] = 0 flag [i][j] = '0' else: if L[j-1] == S[i-1]: dp[i][j] = dp[i-1][j-1] + 1 flag[i][j] = 'ok' else: dp[i][j] = max(dp[i-1][j],dp[i][j-1]) flag[i][j] = 'up' if dp[i][j] == dp[i-1][j] else 'left' return dp[-1][-1],flag def printres(flag,L,S): m = len(flag) n = len(flag[0]) res = '' i , j = m-1 , n-1 while i > 0 and j > 0: if flag[i][j] == 'ok': res += L[j-1] i -= 1 j -= 1 elif flag[i][j] == 'left': j -= 1 elif flag[i][j] == 'up': i -= 1 return res[::-1] L = 'BDCABA' S = 'ABCBDAB' num,flag = LCS(L,S) res = printres(flag,L,S)

 

 


二、題目:最長遞增子序列

給定一個長度為N的陣列,找出一個最長的單調自增子序列(不一定連續,但是順序不能亂)。例如:給定一個長度為6的陣列A{5, 6, 7, 1, 2, 8},則其最長的單調遞增子序列為{5,6,7,8},長度為4.

解法一:最長公共子序列:O(N^2)

這個問題可以轉換為最長公共子序列問題。如例子中的陣列A{5,6, 7, 1, 2, 8},則我們排序該陣列得到陣列A‘{1, 2, 5, 6, 7, 8},然後找出陣列A和A’的最長公共子序列即可。顯然這裡最長公共子序列為{5, 6, 7, 8},也就是原陣列A最長遞增子序列。

解法二:動態規劃法(時間複雜度O(N^2))

 設 dp(j) 表示L中以 L[j] 為末元素的最長遞增子序列的長度。狀態方程:

dp(j) = { max(dp(i)) + 1, i<j且L[i]<L[j] }

這個遞推方程的意思是,在求以L【j】為末元素的最長遞增子序列時,找到所有序號在 j 前面且小於L【j】的元素L【i】,即 i < j 且 L【j】< L【i】。

例如給定的陣列為{5,6,7,1,2,8},則 dp(0)=1, dp(1)=2, dp(2)=3, dp(3)=1, dp(4)=2, dp(5)=4。所以該陣列最長遞增子序列長度為4,序列為{5,6,7,8}。

程式碼:

def LCS1(L):
    if not L:
        return ""
    dp = [1] * len(L)
    for j in range(len(L)):
        for i in range(j):
#當j = 5,i = 0時,dp = [1,2,3,1,2,1]
#當j = 5,i = 0時,dp[5] = 1 < dp[0]+1,故dp(5)更新為dp[0]+1=2,
#當j = 5,i = 1時,dp[5] = 2 < dp[1]+1 =3,故dp(5)更新為dp[1]+1=3
#當j = 5,i = 2時,dp[5] = 4
#當j = 5,i = 3時,dp[5] = 4 > dp[3]+1 = 3,故dp[5]不更新,同理,i = 4時,dp[5]仍等於4
if L[j] > L[i] and dp[j] < dp[i] + 1:
dp[j]
= dp[i]+1 return max(dp) L = [5,6,7,1,2,8] LCS1(L)

得到dp陣列之後找出,最長遞增子序列,

  • 先找到dp最大值5,索引為7,然後arr【7】= 9
  • dp【6】 = 5-1 =4,故arr【6】=8
  • dp【4】 = 4-1或者dp【5】 = 4-1,故arr【4】 = 6 / arr【5】=4
  • dp 【2】=3-1或者dp【3】 = 3-1,故arr【2】 = 5 / arr【3】=3
  • 2 / 1

故最長遞增子序列:2→5→6→8→9或者1→3→4→8→9

 

解法三:優化的動態規劃,時間O(NlogN)