最长递增子序列问题---动态规划
最长递增子序列问题是一个很基本、较常见的小问题,但这个问题的求解方法却并不那么显而易见,需要较深入的思考和较好的算法素养才能得出良好的算法。由于这个问题能运用学过的基本的算法分析和设计的方法与思想,能够锻炼设计较复杂算法的思维,我对这个问题进行了较深入的分析思考,得出了几种复杂度不同算法,并给出了分析和证明。
一, 最长递增子序列问题的描述
设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); } }