1. 程式人生 > >LCS時間複雜度O(NlogN) (LCS 轉 LIS)

LCS時間複雜度O(NlogN) (LCS 轉 LIS)

LCS(Longest Common Subsequences)最長公共子序列用一般的動態規劃時間複雜度O(N^2), 但經過優化可以達到O(NlogN),下面是轉載集訓隊某人的最長遞增子序列解題報告。


 


  先回顧經典的O(n^2)的動態規劃演算法,設A[i]表示序列中的第i個數,F[i]表示從1到i這一段中以i結尾的最長上升子序列的長度,初始時設F[i] = 0(i = 1, 2, ..., len(A))。則有動態規劃方程:F[i] = max{1, F[j] + 1} (j = 1, 2, ..., i - 1, 且A[j] < A[i])。


  現在,我們仔細考慮計算F[i]時的情況。假設有兩個元素A[x]和A[y],滿足


   (1)x < y < i


          (2)A[x] < A[y] < A[i]


          (3)F[x] = F[y]


  此時,選擇F[x]和選擇F[y]都可以得到同樣的F[i]值,那麼,在最長上升子序列的這個位置中,應該選擇A[x]還是應該選擇A[y]呢?


  很明顯,選擇A[x]比選擇A[y]要好。因為由於條件(2),在A[x+1] ... A[i-1]這一段中,如果存在A[z],A[x] < A[z] < a[y],則與選擇A[y]相比,將會得到更長的上升子序列。


  再根據條件(3),我們會得到一個啟示:根據F[]的值進行分類。對於F[]的每一個取值k,我們只需要保留滿足F[i] = k的所有A[i]中的最小值。設D[k]記錄這個值,即D[k] = min{A[i]} (F[i] = k)。


  注意到D[]的兩個特點:


  (1) D[k]的值是在整個計算過程中是單調不上升的。
  (2) D[]的值是有序的,即D[1] < D[2] < D[3] < ... < D[n]。


  利用D[],我們可以得到另外一種計算最長上升子序列長度的方法。設當前已經求出的最長上升子序列長度為len。先判斷A[i]與D [len]。若A[i] > D[len],則將A[i]接在D[len]後將得到一個更長的上升子序列,len = len + 1, D[len] = A[i];否則,在D[1]..D[len]中,找到最大的j,滿足D[j] < A[i]。令k = j + 1,則有D[j] < A[i] <= D[k],將A[i]接在D[j]後將得到一個更長的上升子序列,同時更新D[k] = A[i]。最後,len即為所要求的最長上升子序列的長度。


  在上述演算法中,若使用樸素的順序查詢在D[1]..D[len]查詢,由於共有O(n)個元素需要計算,每次計算時的複雜度是O(n),則整個演算法的時間複雜度為O(n^2),與原來的演算法相比沒有任何進步。但是由於D[]的特點(2),我們在D[]中查詢時,可以使用二分查詢高效地完成,則整個演算法的時間複雜度下降為O(nlogn),有了非常顯著的提高。需要注意的是,D[]在演算法結束後記錄的並不是一個符合題意的最長上升子序列!


  這個演算法還可以擴充套件到整個最長子序列系列問題,整個演算法的難點在於二分查詢的設計,需要非常小心注意。


  


  最長公共子序列向最長遞增子序列退化:


  設有序列A,B。記序列A中各個元素在B 中的位子(降序排列),然後按在A中的位置依次列出按後求A的最長遞增子序列。


  例如:有A={a,b,a,c,x},B={b,a,a,b,c,a}則有a={6,3,2},b={4,1},c={5};x=/;(注意降序排列)


然後按A中次序排出{a(6,3,2),b(4,1),a(6,3,2),c(5),x()}={6,3,2,4,1,6,3,2,5};對此序列求最長遞增子序列即可