最长上升子序列
来源:互联网 发布:mysql版本区别 编辑:程序博客网 时间:2024/05/17 21:39
这是一道老题,有两种思路,时间复杂度分别是O(n^2)和O(nlgn). O(n^2)的方法是典型的DP思路,较为常见,现在整理下O(nlgn)的算法思路。
算法思路
最长上升子序列的查找用到了多个数组。a数组作为存储原始数据的数组,该算法的实现过程就是将a数组中每个元素插入c数组中的过程,而c数组中最后剩余的a数组中的元素个数即是最长上升序列的长度。
原因如下:
最长上升序列中的元素大小一定是递增,利用这一个性质,每次将元素a[i]插入到c数组中时,是插入到一个位置c[j],使得c[j]之前的元素都比a[i]小,使得c[j]之后的元素都比a[i]大,若a[i]已经在c数组中存在了,那就不选择插入。可以得到c数组元素大小是递增的,同时在插入的时候可以使用2分查找,那么时间复杂度就是O(lgn)。那么对于n个数来说, 总的计算最长上升序列的时间复杂度为O(nlgn)。
核心算法描述如下:
for(int i=0; i<a.length ;i++) //O(n){ j=find(c,a.length,a[i]);//O(lgn) c[j]=a[i]; //把a[i]插入到c数组中去 b[i]=j;//a[i]结尾的最长递增序列长度为j;}
把每个a[i]插入到c数组中去之后,返回a[i]在c数组中的位置,因此j就表示了以a[i]为结尾元素的最长上升序列的长度。将j保存在b[i]中,也就是说b[i]保存了以a[i]为结尾元素的最长上升序列的长度。这个数组的使用利于我们之后将最长字符串输出。
字符串的输出
首先找到b[i]数组中的最大值,也就是最长的递增序列长度,然后向回遍历b数组,当b[i]=最大长度-1 并且a[i]小于a[max] (两者缺一不可),那么就表明a[i]就是最长递增序列的倒数第二个数,依次下去,最终找到并输出最长递增序列。参考代码如下:
show[b[max]-1]=a[max];//用来保存最长递增序列 int m =b[max]; //用O(n)时间把最长序列输出 for(int i=max; i>0 ;i--) { if( b[i-1]==(m-1) && (a[i-1]<a[max]))//a[i-1]要是递增序列中的第m-1个数,那么 a[i-1]<a[max] {//a[i-1]是递增序列的第m-1个数 show[m-2]=a[i-1]; max=i-1;//要比较的最大数变为数组中的第i-1个数 m--; }}
下表是一个例子,针对串“9085598863”,算法最终运行结束后各数组的情况。注意c数组内的元素并一定是最长上升子序列(我们对于c数组下标从1开始算起)。
参考代码
/** * 2分查找到以a[i]结尾的最长递增序列在c数组的位置,目的是把a[i]插入到c数组合适的位置 * @return */ public static int find(int c[],int size,int a) { int left =1; int right =size; int mid=(left+right)/2; while(left<=right) { if(a > c[mid]) left=mid+1;//要插入的位置在mid右边; else if(a < c[mid]) right =mid-1;//要插入的位置在mid左边; else return mid;//a已经在c数组中,直接返回a在数组中的位置 mid =(left+right)/2; } return left;//插入到最左边或者最右边的情况 } /** * 查找最长上升序列 * @param a 保存了原始序列的数组 * @param b 保存了以a[i]为最后一个字符的最长上升序列的长度 * @param c 保存了长度为i增长序列的为最后一个字符的最小值,c是一个递增数组 */ public static int longSeq(int a[],int b[],int c[]) { for(int i=0;i<=a.length;i++) c[i]=10000; c[0]=-1; c[1]=a[0]; b[0]=1; int j=-1; for(int i=0; i<a.length ;i++) //O(n) { j=find(c,a.length,a[i]);//O(lgn) c[j]=a[i]; //把a[i]插入到c数组中去 b[i]=j;//a[i]结尾的最长递增序列长度为j; } int max =-1; for(int i=1;i<=a.length;i++) { if(c[i] != 10000) max =i; //记录了其长度为i递增序列,其最后一个数为c[i]; else break; } return max; } //找到最长递增序列,show数组保存递增序列 public static void showLongSeq(int a[],int b[],int show[]) { int d[] =new int[b.length]; //将b[]数组复制到d[]数组中 for(int i=0;i<b.length;i++) { d[i]=b[i]; } //用O(n)时间找出b数组中的最大值,max代表最大数的下标 int max=0; int temp =-1; for(int i=0;i<d.length-1;i++) { if(d[i]>d[i+1]) { temp =d[i]; d[i] =d[i+1]; d[i+1]=temp; }else max =i+1; } show[b[max]-1]=a[max];//用来保存最长递增序列 int m =b[max]; //用O(n)时间把最长序列输出 for(int i=max; i>0 ;i--) { if( b[i-1]==(m-1) && (a[i-1]<a[max]))//a[i-1]要是递增序列中的第m-1个数,那么 a[i-1]<a[max] { //a[i-1]是递增序列的第m-1个数 show[m-2]=a[i-1]; max=i-1;//要比较的最大数变为数组中的第i-1个数 m--; } } for(int i=0; i<show.length;i++) { System.out.print(show[i]+" "); } } public static void main(String[] args) { int a[]={13,8,7,11,13,14 ,13 ,16, 15 ,9 ,18 ,9 ,2, 2 ,3 ,7 ,5 ,1, 1}; int b[]=new int[a.length]; int c[]=new int[a.length+1]; int max = longSeq(a,b,c); System.out.println(max ); int show[] =new int[max]; showLongSeq(a,b,show); }
代码只是简单实现该算法,其中有很多不规范的地方还请见谅。
跟传统的O(n^2)时间复杂度算法相比,该算法主要利用了递增子序列的性质,通过二分查找的方法加快算法,降低了时间复杂度。最终的时间复杂度为O(nlgn)。值得一提的是,《编程之美》中也有这道题目,思路跟本方法大同小异。
- 最长上升子序列
- 最长上升子序列
- 最长上升子序列
- 最长上升子序列
- 最长上升子序列
- 最长上升子序列
- 最长上升子序列
- 最长上升子序列
- 最长上升子序列
- 最长上升子序列
- 最长上升子序列
- 最长上升子序列
- 最长上升子序列
- 最长上升子序列
- 最长上升子序列
- 最长上升子序列
- 最长上升子序列
- 最长上升子序列
- JPA基础(一):全面阐释和精彩总结JPA
- 【C语言】编写函数实现字符串旋转
- 循环双链表的删除、插入、显示
- 一起talk GDB吧(第二回:GDB单步调试)
- 【C语言】编写函数实现库函数atoi,把字符串转换成整形
- 最长上升子序列
- C++ Builder XE7 调用JAVA的JAR文件
- AFNetworking的POST方法(自带cookie)
- 关于autorelease pool一个较好的理解
- struts2 java.lang.ClassNotFoundException: org.apache.commons.lang.xwork.StringUtils
- 不要什么都想要,否则你会焦虑
- 上海之行开会感悟
- java中顶级父类Object
- AutoReleasePool 和 ARC 以及Garbage Collection