最长升序子序列,很经典的问题,发现写博客的真是不负责任啊,瞎写一通根本没有任何逻辑,本文主要参考http://yzmduncan.iteye.com/blog/1546503,加上自己的理解和实现的代码。
最长递增子序列问题:在一列数中寻找一些数,这些数满足:任意两个数a[i]和a[j],若i<j,必有a[i]<a[j],这样最长的子序列称为最长递增子序列。设dp[i]表示以i为结尾的最长递增子序列的长度,则状态转移方程为:
dp[i] = max{dp[j]+1}, 1<=j<i,a[j]<a[i].
public static int lengthOfLIS(int[] nums) { if(nums.length<2){ return nums.length; } int[] count=new int[nums.length]; for(int i=0; i<count.length; i++){ count[i]=1; } int re=1; for(int i=1; i<nums.length; i++){ int max=0; for(int j=0; j<i; j++){ if(nums[j]<nums[i]&&count[j]>max){ max=count[j]; } } count[i]=max+1; } for(int i=0; i<count.length; i++){ if(count[i]>re){ re=count[i]; } } return re; }
考虑两个数a[x]和a[y],x<y且a[x]<a[y],且dp[x]=dp[y],当a[t]要选择时,到底取哪一个构成最优的呢?显然选取a[x]更有潜力,因为可能存在a[x]<a[z]<a[y],这样a[t]可以获得更优的值。在这里给我们一个启示,当dp[t]一样时,尽量选择更小的a[x].
按dp[t]=k来分类,只需保留dp[t]=k的所有a[t]中的最小值,设d[k]记录这个值,d[k]=min{a[t],dp[t]=k}。
这时注意到d的两个特点(重要):
1. d[k]在计算过程中单调不升;
2. d数组是有序的,d[1]<d[2]<..d[n]。
利用这两个性质,可以很方便的求解:
1. 设当前已求出的最长上升子序列的长度为len(初始时为1),每次读入一个新元素x:
2. 若x>d[len],则直接加入到d的末尾,且len++;(利用性质2)
否则,在d中二分查找,找到第一个比x小的数d[k],并d[k+1]=x,在这里x<=d[k+1]一定成立(性质1,2)。
private static int binarySearch(int[] nums, int i, int j, int target){ while(i<j){ int mid=(i+j)>>>1; if(target>nums[mid]&&target<=nums[mid+1]){ return mid; }else if(nums[mid]<target){ i=mid+1; }else{ j=mid-1; } } return 0; } public static int lengthOfLIS2(int[] nums) { if(nums.length<2){ return nums.length; } int len=1; int[] d=new int[nums.length+1]; d[1]=nums[0]; for(int i=1; i<nums.length; i++){ if(nums[i]>d[len]){ d[++len]=nums[i]; }else{ int j=binarySearch(d, 0, len, nums[i]); d[++j]=nums[i]; }
<span style="white-space:pre"> </span>//System.out.println(Arrays.toString(d)); } return len; }
int nums[]={10, 9, 2, 5, 3, 7, 101, 18}; 在代码中注释部分,每次都打印出d[]的值。
[0, 9, 0, 0, 0, 0, 0, 0, 0]