一,問題描述
給定一個序列,求解它的最長 遞增 子序列 的長度。比如: arr[] = {3,1,4,1,5,9,2,6,5} 的最長遞增子序列長度為4。即為:1,4,5,9
二,算法分析
有兩種方式來求解,一種是轉化為LCS問題。即,首先對數組排序,將排序后的結果存儲在輔助數組中。排序時間復雜度O(NlogN),排序后的數組與原數組組成了LCS(N,N)問題。解決LCS問題的時間復雜度為O(N^2),故整個算法的時間復雜度為O(N^2),空間復雜度為O(N)
另一種方式是直接用DP求解,算法如下:時間復雜度為O(N^2)
①最優子問題
設lis[i] 表示索引為 [0...i] 上的數組上的 最長遞增子序列。初始時,lis[i]=1,注意,在DP中,初始值是很重要的,它是整個算法運行正確的關鍵。而初始值 則可以 通過 畫一個小的示例來 確定。
當 arr[i] > arr[j],lis[i] = max{lis[j]}+1 ;其中,j 的取值范圍為:0,1...i-1
當 arr[i] < arr[j],lis[i] = max{lis[j]} ;其中,j 的取值范圍為:0,1...i-1
②重疊子結構
從上面可以看出,計算 lis[i]時,需要計算 lis[j],其中 j < i,這說明有重疊子問題。借用網路中一張圖如下:
lis(4) / | \ lis(3) lis(2) lis(1) / \ / lis(2) lis(1) lis(1) / lis(1)
而初始值 則可以 通過 畫一個小的示例來 確定。
參考資料:
三,代碼實現
錯誤版本1:
1 private static int lis(int[] arr, int length){ 2 int lis[] = new int[length]; 3 4 //init 5 for(int i = 0; i < length; i++) 6 lis[i] = 1; 7 8 for(int i = 1; i < length; i++) 9 { 10 for(int j = 0; j < i; j++) 11 { 12 13 if(arr[i] > arr[j]) 14 { 15 if(lis[j] + 1 > lis[i]) 16 lis[i] = lis[j] + 1; 17 } 18 else{ 19 if(lis[j] > lis[i]) 20 lis[i] = lis[j]; 21 } 22 } 23 } 24 return lis[length - 1]; 25 }
第13行if語句會導致bug,arr[i]要大於 j belongs to 0,1,...i-1 中所有的 arr[j]中的最大值,並且 lis[i] 是該最大值 arr[j] 所對應的 lis[j] +1,而不是某個其他的arr[j] 對應的 lis[j]+1
正確完整版本:
public class LIS { public static int lis(int[] arr){ if(arr == null || arr.length == 0) return 0; return lis(arr, arr.length); } private static int lis(int[] arr, int length){ int lis[] = new int[length]; //init for(int i = 0; i < length; i++) lis[i] = 1; for(int i = 1; i < length; i++) { for(int j = 0; j < i; j++) { // lis[i]=max{lis[i-1], lis[i-1]+1} if(arr[i] > arr[j] && lis[j] + 1 > lis[i]) lis[i] = lis[j] + 1; } } int max = lis[0]; for(int i = 1; i < length; i++) if(max < lis[i]) max = lis[i]; return max; } public static void main(String[] args) { int[] arr = {3,1,4,1,5,9,2,6,5}; int result = lis(arr); System.out.println(result); } }