牛客刷题——最长上升子序列

题目:给定数组arr,设长度为n,输出arr的最长递增子序列。(如果有多个答案,请输出其中字典序最小的)

eg: 输入:[2,1,5,3,6,4,8,9,7]

输出:[1,3,4,8,9]

这题好不容易写出了动归,结果超时了。。。。难过

说一下另一种解法,还是看别人的代码理解了很久。贪心+二分。

首先在动归中有一个操作,是查找当前元素位置之前,第一个小于当前元素的值,正是这个查询操作导致动归解法的时间复杂度变成了O(n^{2}),所以这里用二分查找来实现以减少时间复杂度。(动归+二分应该也是可以?我没有尝试,觉得这种贪心的解法挺有趣的就记录一下)

然后说一下具体的解法,首先使用一个数组 lens 来存储当前元素所能构成的最长上升子序列,然后使用一个数组 flags 来存储上升子序列的临时元素,但是要注意这个数组中的元素并不一定是最终结果,其作用主要是辅助更新 lens 数组,最终的最长上升子序列的构成也是由 lens 和原始数组 arr 生成的。

首先原始数组为 arr = {2,1,5,3,6,4,8,9,7},我们定义一个最大长度max来记录所能达到的最大上升子序列的长度,初始化 flags = {2,0,0,0,0,0,0,0,0},lens = {1,0,0,0,0,0,0,0,0},使用循环从第1个元素开始遍历每个元素(数组元素从第0个开始计算),每当到达一个元素i,判断当前元素 arr[i] 和 flags[max-1] 的大小(这里的 flags[max-1] 是flags的最后一个元素有效元素),

1. 如果二者相等的话,直接将 lens[i] 置为max,因为此时所能构成的最大长度是不变的,大小相等也不需要对元素进行替换;

2. 如果当前元素 arr[i] 大于 flags[max-1],那么所能构成的最大长度 max++,flags[max-1]置为当前元素,并且更新 lens[i] 为 max;

3. 如果当前元素 arr[i] 是小于 flags[max-1]的,那么说明最大长度不变,但是所构成的子序列中,arr[i] 作为候选元素,可能可以去替换序列中的某个元素,因此需要在 flags 中使用二分进行查找,找到第一个小于 arr[i] 的元素下标 index 进行替换,替换之后,更新 lens[i] 为 index+1(按照之前提到的 flags 中的元素位置是和最长上升子序列绑定的,flags中的位置实际就是最长上升子序列的位置,因此 index 表示了从0到index的子序列的长度-1,所以 index+1 表示的是到元素 flags[i] 所能构成的子序列的长度),这一块当时理解费了点力气。。

经过上述步骤,可以计算出 lens = {1,1,2,2,3,3,4,5,4},然后再次使用贪心算法获得最终结果。

由于题目要求的是最小字典,我们在计算 flags 和 lens 的时候已经按照这个逻辑进行了,并且我们得到了最大长度是5,我们在 lens 中可以看到,存在多个元素所能构成的最大长度相等的情况,那么应该怎么去判断哪个是小字典的呢。

首先明确 lens 值相等有两种情况,1. 元素值相等,那么能够构成的最长序列的长度自然相等,这种情况对于字典大小是没有影响的;2.后面元素的值小于前面元素的值,更新 lens 后二者的值相等。

从上面的分析就可以得到,第一种情况可以忽略不计,那相等的来源就是第二种情况,以及在对第二种情况的分析中我们也知道了,对于 lens  相等的元素,越靠后的值是越小的,那么我们就可以从后向前遍历,从 lens 值等于max 的位置开始,判断当前值是不是等于 max,是的话直接拿到当前元素,max--,这样也就直接跳过了前面的等于max的值,进入了下一个位置的挑选。

即选出的 lens 中的元素对应的位置为 1,3,5,6,7,对应 arr 中的元素为 1,3,4,8,9.

完整代码如下:

public static int[] LIS (int[] arr) {
        // write code here
		int max = 1;  //记录最大长度
		int[] lens = new int[arr.length];  //记录i能构成的最大长度
		int[] flags = new int[arr.length];  //记录最长序列
		flags[0] = arr[0];  //初始化最长序列
		lens[0] = 1;
		for(int i=1;i<arr.length;i++) {  
			if(flags[max-1]<arr[i]) { //当前元素大于最长序列,可以构成,直接放在后面
				flags[max++] = arr[i];
				lens[i] = max;
			}else if(flags[max-1]>arr[i]){//当前元素小于最长序列,往前查找进行替换
				int index = findFirst(flags,max,arr[i]);  //更新的是最长序列的元素,不是原始数组
				flags[index] = arr[i];	
				lens[i] = index+1;  //index表示的位置,+1后表示的是长度
			}else {
				lens[i] = max;
			}
		}
		
		
		int[] res = new int[max];
		for(int i = arr.length-1;i>=0;i--) {
			if(lens[i]==max) {
				res[--max] = arr[i];
			}
		}
		
		return res;
	}
	public static int findFirst(int[] arr,int i, int index) { //查找lens前i-1个元素中小于index的最长序列
		int left = 0;
		int right = i-1;
		while(left<=right) {
			int mid = (left+right)/2;
			if(arr[mid]<index) {
				left = mid+1;
			}else{
				right = mid-1;
			}
		}
		return left;
	}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值