最長遞增子序列問題是一個很基本、較常見的小問題,但這個問題的求解方法卻並不那么顯而易見,需要較深入的思考和較好的算法素養才能得出良好的算法。由於這個問題能運用學過的基本的算法分析和設計的方法與思想,能夠鍛煉設計較復雜算法的思維,我對這個問題進行了較深入的分析思考,得出了幾種復雜度不同算法,並給出了分析和證明。
一, 最長遞增子序列問題的描述
設L=<a1,a2,…,an>是n個不同的實數的序列,L的遞增子序列是這樣一個子序列Lin=<aK1,ak2,…,akm>,其中k1<k2<…<km且aK1<ak2<…<akm。求最大的m值。
二, 第一種算法:轉化為LCS問題求解
設序列X=<b1,b2,…,bn>是對序列L=<a1,a2,…,an>按遞增排好序的序列。那么顯然X與L的最長公共子序列即為L的最長遞增子序列。這樣就把求最長遞增子序列的問題轉化為求最長公共子序列問題LCS了。
最長公共子序列問題用動態規划的算法可解。設Li=< a1,a2,…,ai>,Xj=< b1,b2,…,bj>,它們分別為L和X的子序列。令C[i,j]為Li與Xj的最長公共子序列的長度。則有如下的遞推方程:
這可以用時間復雜度為O(n2)的算法求解,由於這個算法上課時講過,所以具體代碼在此略去。求最長遞增子序列的算法時間復雜度由排序所用的O(nlogn)的時間加上求LCS的O(n2)的時間,算法的最壞時間復雜度為O(nlogn)+O(n2)=O(n2)。
三, 第二種算法:動態規划法
設f(i)表示L中以ai為末元素的最長遞增子序列的長度。則有如下的遞推方程:
這個遞推方程的意思是,在求以ai為末元素的最長遞增子序列時,找到所有序號在L前面且小於ai的元素aj,即j<i且aj<ai。如果這樣的元素存在,那么對所有aj,都有一個以aj為末元素的最長遞增子序列的長度f(j),把其中最大的f(j)選出來,那么f(i)就等於最大的f(j)加上1,即以ai為末元素的最長遞增子序列,等於以使f(j)最大的那個aj為末元素的遞增子序列最末再加上ai;如果這樣的元素不存在,那么ai自身構成一個長度為1的以ai為末元素的遞增子序列。
public void lis(float[] L) { int n = L.length; int[] f = new int[n];//用於存放f(i)值; f[0]=1;//以第a1為末元素的最長遞增子序列長度為1; for(int i = 1;i<n;i++)//循環n-1次 { f[i]=1;//f[i]的最小值為1; for(int j=0;j<i;j++)//循環i 次 { if(L[j]<L[i]&&f[j]>f[i]-1) f[i]=f[j]+1;//更新f[i]的值。 } } System.out.println(f[n-1]); }
最長遞增子序列1---求最長公共子序列的長度:
給定一個長度為N的數組,找出一個最長的單調自增子序列(不一定連續,但是順序不能亂)
例如:給定一個長度為8的數組A{1,3,5,2,4,6,7,8},則其最長的單調遞增子序列為{1,2,4,6,7,8},長度為6.
輸入描述:
第一行包含一個整數T,代表測試數據組數。
對於每組測試數據: N-數組的長度
a1 a2 ... an (需要計算的數組)
保證:
1<=N<=3000,0<=ai<=MAX_INT.
輸出描述:
對於每組數據,輸出一個整數,代表最長遞增子序列的長度。
輸入例子:
2 7 89 256 78 1 46 78 8 5 6 4 8 2 17
解題思路:采用動態規划的方法來解,如下:
| 數組array | ai | 89 | 256 | 78 | 1 | 46 | 78 | 8 |
| 長度list | len(ai) | 1 | 2 | 1 | 1 | 2 | 3 | 2 |
import java.util.*; public class Main{ public static void main(String[] args){ Scanner scan = new Scanner(System.in); int T = scan.nextInt(); // System.out.println(T); for(int i=0;i<T;i++){ int N = scan.nextInt(); // System.out.println(N); int[] num = new int[N]; for(int j=0;j<N;j++){ num[j] = scan.nextInt(); } System.out.println(lengthOfMaxSubIncreaseArray(num, N)); } } public static int lengthOfMaxSubIncreaseArray(int[] array, int n){ if(n==1){ return 1; } int maxLen = 0; int[] list = new int[n]; for(int i=0;i<n;i++){ list[i]=1; for(int j=0;j<i;j++){ if(array[j]<array[i]&&list[j]+1>list[i]){ list[i] = list[j]+1; } } } for(int i=0;i<n;i++){ if(list[i]>maxLen) maxLen = list[i]; } return maxLen; } }
最長遞增子序列2----求最長公共子序列的第一組序列:
給定一個長度為N的數組,找出一個最長的單調自增子序列(不一定連續,但是順序不能亂)
例如:給定一個長度為8的數組A{1,3,5,2,4,6,7,8},則其最長的單調遞增子序列為{1,2,4,6,7,8},長度為6.
輸入描述:
第一行包含一個整數T,代表測試數據組數。
對於每組測試數據:
N-數組的長度
a1 a2 ... an (需要計算的數組)
保證:
1<=N<=3000,0<=ai<=MAX_INT.
輸出描述:
對於每組數據,輸出一個整數序列,代表最長遞增子序列。
若有多組最長上升子序列,輸出第一組。
保證:1<=T<=20,1<=N<=3000,0<=ai<=MAX_INT.
輸入例子:
2 7 89 256 78 1 46 78 8 5 6 4 8 2 17
輸出例子:
1 46 78 6 8 17
import java.util.*; public class Main{ public static void main(String[] args){ Scanner scan = new Scanner(System.in); int T = scan.nextInt(); // System.out.println(T); for(int i=0;i<T;i++){ int N = scan.nextInt(); // System.out.println(N); int[] num = new int[N]; for(int j=0;j<N;j++){ num[j] = scan.nextInt(); } System.out.println(maxSubIncreaseArray(num, N)); } }public static ArrayList<Integer> maxSubIncreaseArray(int[] array, int n){ int[] list = new int[n]; ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>(); ArrayList<Integer> tmp = new ArrayList<Integer>(); int index = -1;//用於標記當前元素之前的第一個遞增子序列的位置 int maxIndex = 0;//用於標記該序列的最長遞增子序列的位置 int max = Integer.MIN_VALUE;//最長遞增子序列的長度 list[0] = 1;//該列表用於標記包括當前元素在內的前半部分的最長遞增子序列的長度 tmp.add(array[0]); res.add(tmp); for(int i=1;i<n;i++){ index = -1; tmp = new ArrayList<Integer>(); for(int j=0;j<i;j++){ if(array[j]<array[i]&&list[j]>list[i]){ list[i] = list[j]; index = j; } } ++list[i]; if(index>-1) tmp.addAll(res.get(index)); tmp.add(array[i]); res.add(tmp); if(list[i]>max){ max = list[i]; maxIndex = i; } } return res.get(maxIndex); } }
