300. Longest Increasing Subsequence

最长升序子序列,很经典的问题,发现写博客的真是不负责任啊,瞎写一通根本没有任何逻辑,本文主要参考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;
    }


这样简单的复杂度为O(n^2),其实还有更好的方法。

考虑两个数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;
    }


以上是自己实现的代码,这个算法关键的地方在于辅助的数组d[],它的意义表示,d[k]表示长度为K的最长字串的最大值,len表示最大长度,那么最大长度字串的最大值就是d[len].原理上面已经说的很明白,但是我第一次也是花了很长时间才看明白,举个例子,结合计算过程更容易理解。以leetcode上的例子为例,

int nums[]={10, 9, 2, 5, 3, 7, 101, 18};  在代码中注释部分,每次都打印出d[]的值。

[0, 9, 0, 0, 0, 0, 0, 0, 0]
[0, 2, 0, 0, 0, 0, 0, 0, 0] //这步体现出了性质2,len的长度没有变,但是同样长度为1,2比9更有潜力,所以把长度为1的字串最大值更新为2.
[0, 2, 5, 0, 0, 0, 0, 0, 0]//这步5比上一步求出的最大字串的最大值2还大,所以len++,d[len]更新为5
[0, 2, 3, 0, 0, 0, 0, 0, 0]
[0, 2, 3, 7, 0, 0, 0, 0, 0]
[0, 2, 3, 7, 101, 0, 0, 0, 0]
[0, 2, 3, 7, 18, 0, 0, 0, 0]


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值