【个人笔记】牛客题目:NC91 最长上升子序列(三)

描述
给定数组arr,设长度为n,输出arr的最长上升子序列。(如果有多个答案,请输出其中按数值(注:区别于按单个字符的ASCII码值)进行比较的字典序最小的那个)
数据范围:0≤n≤200000 , 0≤arr_i≤1000000000
要求:空间复杂度 O(n),时间复杂度 O(nlogn)

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

输入:[1,2,8,6,4]
返回值:[1,2,4]
说明:其最长递增子序列有3个,(1,2,8)、(1,2,6)、(1,2,4),其中第三个按数值进行比较的字典序最小,故答案为(1,2,4)

解题思路(参考了牛客中的解答和其他人的解答):
使用贪心+二分法
贪心地希望tails能够更长且已经构成tails的元素更小,所以二分地从tails中找第一个大于等于的元素替换,如果没有就说明需要更新长度;
eg:有以下序列A[ ] = 1 4 7 2 5 9 10 3,求LIS长度。
A[1] = 1,把1放进B[1],此时B[1] = 1,B[ ] = {1},len = 1
A[2] = 4,把4放进B[2],此时B[2] = 4,B[ ] = {1,4},len = 2
A[3] = 7,把7放进B[3],此时B[3] = 7,B[ ] = {1,4,7},len = 3
A[4] = 2,因为2比4小,所以把B[2]中的4替换为2,此时B[ ] = {1,2,7},len = 3
A[5] = 5,因为5比7小,所以把B[3]中的7替换为5,此时B[ ] = {1,2,5},len = 3
A[6] = 9,把9放进B[4],此时B[4] = 9,B[ ] = {1,2,5,9},len = 4
A[7] = 10,把10放进B[5],此时B[5] = 10,B[ ] = {1,2,5,9,10},len = 5
A[8] = 3,因为3比5小,所以把B[3]中的5替换为3,此时B[ ] = {1,2,3,9,10},len = 5
  最终得出LIS长度为5。但是!!这里的1 2 3 9 10并不是正确的最长上升子序列。B序列并不一定表示最长上升子序列,它只表示相应最长子序列长度的排好序的最小序列。
(以上来自https://blog.youkuaiyun.com/lxt_Lucia/article/details/81206439
本题需要得到最小字典序的子序列,所以在以上基础上进行修改;
记录curLen[i]表示以当前元素arr[i]为结尾的最长递增子序列的长度
根据curLen[i]逆序地从最大长度开始遍历,即满足curLen[i] == ans条件的元素,就是长度为ans的字典序最小的方案

class Solution {
public:
    /**
     * retrun the longest increasing subsequence
     * @param arr int整型vector the array
     * @return int整型vector
     */
    vector<int> LIS(vector<int>& arr) {
        // write code here
        vector<int> tail(arr.size());  // 贪心+二分得到的子序列
        vector<int> curlen(arr.size()); // curlen[i]表示以arr[i]为结尾的元素对应的最长上升子序列长度
        // 遍历arr
        int tail_size = 0; // 因为tail初始化了一个固定的大小,所以得有一个表示tail当前大小的数
        int left, right, mid;
        for(int i=0; i<arr.size(); i++){
            // 二分法从tail中找第一个比arr[i]大的数
            left=0;
            right = tail_size;
            while(left<right){
                mid = left + (right-left)/2;
                if(tail[mid] >= arr[i])
                    right = mid;
                else
                    left = mid + 1;
            }
            // 若找到,找到后用arr[i]将那个较大的数替换掉;
            // 若找不到,说明tail所有数都比arr[i]小,则把arr[i]加入末尾
            tail[left] = arr[i]; 
            curlen[i] = left + 1;
            if(tail_size==left) // 找不到的情况,tail大小+1
                tail_size++;
        }
        /*
		eg:[2,1,5,3,6,4,8,9,7]
		arr[0]:2, tail:[2], curLen[0]:1
		arr[1]:1, tail:[1], curLen[1]:1
		arr[2]:5, tail:[1,5], curLen[2]:2
		arr[3]:3, tail:[1,3], curLen[3]:2
		arr[4]:6, tail:[1,3,6], curLen[4]:3
		arr[5]:4, tail:[1,3,4], curLen[5]:3
		arr[6]:8, tail:[1,3,4,8], curLen[6]:4
		arr[7]:9, tail:[1,3,4,8,9], curLen[7]:5
		arr[8]:7, tail:[1,3,4,7,9], curLen[8]:4  (8被替换成7,是以7为结尾的长度)
		ans = 5
		*/
        vector<int> res(tail_size); // 存放结果,虽然tail数组不是最终结果,但tail的大小为结果长度(最长上升子序列长度)
        // 逆序地从最大长度开始遍历
        int i,j;
        for(i=arr.size()-1, j=tail_size; i>=0; i--){
            if(curlen[i]==j)  // 以arr[i]为结尾的最长子序列长度为j说明arr[i]即为res的第j-1位
                // 因为相同长度(curlen相同)的情况下,越往后(i越大)的末尾元素越小(大的被小的替换了),所以从后往前取值可得到题目要求的结果
                res[--j] = arr[i];
            //j=5, curLen[i=7]:5, res[4]=arr[7], res: 0 0 0 0 9
			//j=4, curLen[i=6]:4, res[3]=arr[6], res: 0 0 0 8 9
			//j=3, curLen[i=5]:3, res[2]=arr[5], res: 0 0 4 8 9
			//j=2, curLen[i=3]:2, res[1]=arr[3], res: 0 3 4 8 9
			//j=1, curLen[i=1]:1, res[0]=arr[1], res: 1 3 4 8 9
        }
        return res;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值