最长递增子序列(LIS)
最长递增子序列(Longest Increasing Subsequence) ,我们简记为 LIS。
题:求一个一维数组arr[i]中的最长递增子序列的长度,如在序列1,-1,2,-3,4,-5,6,-7中,最长递增子序列长度为4,序列为1,2,4,6。
解法一:快速排序+LCS
刚开始做这道题的时候,由于之前做过几道LCS的题,于是最先想到的是快速排序+LCS的方法。这种方法解决了当时只计算单个case的问题,但是后来面对计算多个 case的问题的时候,第一次遇到Memory Limit Exceeded。于是就意识到这种简单解法的时间和空间的复杂度都太高了。于是只能另寻他法。
public class Main { static int n; static int[] a; static int[] b; static int[][] z; public static void QuickSort(int[] a){ QSort(a,1,n); } public static void QSort(int[] a,int p,int r){ if(p<r) { int q=Partition(a,p,r); QSort(a,p,q-1); QSort(a,q+1,r); } } public static int Partition(int[] a,int p,int r){ int x=a[r]; int i=p-1; for(int j=p;j<r;j++) { if(a[j]<=x){ i=i+1; swap(a, i, j); } } swap(a, i+1, r); return i+1; } public static void swap(int[] a, int i,int j){ int temp; temp=a[j]; a[j]=a[i]; a[i]=temp; } public static int LCS(int a[],int[] b){ z=new int [n+1][n+1]; int i,j; for( i=0;i<=n;i++) z[i][0]=0; for( j=0;j<=n;j++) z[0][j]=0; for(i=1;i<=n;i++){ for( j=1;j<=n;j++){ if(a[i]==b[j]){ z[i][j]= z[i-1][j-1]+1; } else z[i][j]=z[i-1][j] > z[i][j-1] ?z[i-1][j]:z[i][j-1]; } } return z[n][n]; } public static void main(String[] args) { int arr[] = {1,-1,2,-3,4,-5,6,-7}; n=arr.length; a=new int[n+1]; b=new int[n+1]; int i,j; for(i=1;i<=n;i++){ b[i]=a[i]; } QuickSort(a); //控制严格递增 for(i=1;i<n;i++){ for(j=i+1;j<=n;j++){ if(a[i]!=-1 && a[i]==a[j]) a[j]=-1; } } System.out.println(LCS(a,b)); } }
解法二:DP(O(N^2))
LIS[i+1] = max{1,LIS[k]+1},aray[k],for any k <=i.
即如果array[i+1]大于array[k],那么第i+1个元素可以接在LIS[k]长的子序列后面构成一个更长的子序列。于此同时array[i+1]本身至少可以构成一个长度为1的子序列。
int dp[40]; /* dp[i]记录到[0,i]数组的LIS */
int lis; /* LIS 长度 */
解法三:二分查找+DP(O(nlogn))
在解法二中,当考察第i+1个元素的时候,我们是不考虑前面i个元素的分布情况的。现在我们从另一个角度分析,即当考察第i+1个元素的时候考虑前面i个元素的情况。
目的:我们期望在前i个元素中的所有长度为len的递增子序列中找到这样一个序列,它的最大元素比arr[i+1]小,而且长度要尽量的长,如此,我们只需记录len长度的递增子序列中最大元素的最小值就能使得将来的递增子序列尽量地长。
方法:维护一个数组MaxV[i],记录长度为i的递增子序列中最大元素的最小值,并对于数组中的每个元素考察其是哪个子序列的最大元素,二分更新MaxV数组,最终i的值便是最长递增子序列的长度。
仔细的分析请看最长递增子序列 O(NlogN)算法,
public class LIS { /* 最长递增子序列 LIS * 设数组长度不超过 30 * DP + BinarySearch */ static int[] MaxV=new int[30]; /* 存储长度i+1(len)的子序列最大元素的最小值 */ static int len; /* 存储子序列的最大长度 即MaxV当前的下标*/ static int BinSearch(int[] MaxV, int size, int x){ /* 返回MaxV[i]中刚刚不小于x的那个元素的下标 */ int left = 0, right = size-1; while(left <= right){ int mid = (left + right) / 2; if(MaxV[mid] <= x){ left = mid + 1; }else{ right = mid - 1; } } return left; } static int getLIS(int[] arr, int size){ MaxV[0] = arr[0]; /* 初始化 */ len = 1; for(int i = 1; i < size; ++i){ /* 寻找arr[i]属于哪个长度LIS的最大元素 */ if(arr[i] > MaxV[len-1]){ /* 大于最大的自然无需查找,否则二分查其位置 */ MaxV[len++] = arr[i]; }else{ int pos = BinSearch(MaxV,len,arr[i]); MaxV[pos] = arr[i]; } } return len; } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub int arr[] = {1,-1,2,-3,4,-5,6,-7}; /* 计算LIS长度 */ System.out.println(getLIS(arr,arr.length)); } }
参考资料:
《编程之美》 2.16
Felix’s Blog:最长递增子序列 O(NlogN)算法
勇幸|Thinking (http://www.ahathinking.com)
版权声明:本文为博主原创文章,未经博主允许不得转载。
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 软件产品开发中常见的10个问题及处理方法
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· 一次Java后端服务间歇性响应慢的问题排查记录
· dotnet 源代码生成器分析器入门
· ThreeJs-16智慧城市项目(重磅以及未来发展ai)
· 软件产品开发中常见的10个问题及处理方法
· Vite CVE-2025-30208 安全漏洞
· 互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(四):结合BotSharp
· MQ 如何保证数据一致性?