记编程之美的一道经典问题,最长上升子序列问题。

13 篇文章 1 订阅

**给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。**

这个问题,是我在刷leetcode的时候碰到的,我一回想,在编程之美上遇到过,果断翻开手机的PDF,按上面的方法一步步码出了我的代码。
编程之美的思路分三步:
1.提出最基本的算法
那就是线性嵌套遍历,维持一个LIS数组,代表到每一个索引为之的最长子序列的长度,更新它的方式就是在for(int i=0;i<nums.size();++i)中再嵌套一层从0到i的循环,用来更新LIS数组。这个算法复杂度为O(N^2),不得不说毫无新意,一点也不让人激动。
2.做出进一步的优化
通过思考可以得出,相同长度的子序列可能会有好多个,我们没必要跟相同长度下所有的子序列都进行一番比较,我们只需要跟这些子序列中末值最小的那一个序列进行比较就行了,那么我们就需要再维持一个数组maxV,maxV[i]就用来表示长度为i的子序列中最大值的最小值(这里有点绕啊,想清楚了再往下看)。有了这个数组,我们的第二重遍历就不用从0-i,而改为遍历maxV就行了,maxV的长度最多为maxLen,而maxLen肯定<=nums.size(),因此这样就减少了判断次数,但主题还是O(N^2)的复杂度,还是略显鸡肋。
3.探究maxV的规律
研究maxV之后,我们可以得出结论,maxV必须是递增的!为什么呢?因为如果i < j,而maxV[i]>maxV[j]的话,那就说不通了。你想,假设maxV[3]=3;那么我们来求maxV[2]的值,这个maxV[2]最不济也得小于3,因为我们可以从maxV[3]的这个序列中再分个子序列出来嘛。也就是说,如果i<j,那么maxV[i]至少可以是max[j]的子序列,所以maxV[i]<maxV[j]是一定的。既然这样,我们在遍历maxV的时候就可以用二分法了!哟西,成功把复杂度降为了O(N*lgN),激动的我都要尿出来了。
但是!!!就在我一直以来都为编程之美的环环入扣,循循渐进而倾心不已时,leetcode的一个解法狠狠的打醒了我。编程之美的方法通过测试用例都要4ms,代码量也要十几行,而leetcode上排第一的解法居然0ms即可通过,最关键的,它的代码行数,只有个位数!!!
编程之美在我心中的地位彻底崩塌了,以后,leetcode才是我心中的神。。。编程之美,你大爷。。。枉我以熟读你为自豪那么久。。。
编程之美解法
4ms

int lengthOfLIS(vector<int>& nums) {
        if(nums.empty())
            return 0;
        int n=nums.size();
        vector<int>LIS(n,1);
        vector<int>maxV{INT_MIN,nums[0]};
        int maxLen=1;
        for(int i=1;i<n;++i){
            vector<int>::iterator iter=lower_bound(maxV.begin(),maxV.end(),nums[i]);
            int j=distance(maxV.begin(),iter);
            LIS[i]=j;
            if(LIS[i]>maxLen){
                maxLen=LIS[i];
                maxV.push_back(nums[i]);
            }else if(maxV[j]>nums[i]){
                maxV[j]=nums[i];
            }
        }
        return maxLen;
    }

leetcode第一解法
0ms

    int lengthOfLIS(vector<int>& nums){
        vector<int> dp;
        for (int i = 0; i < nums.size(); ++i){
            auto it = lower_bound(dp.begin(), dp.end(), nums[i]);
            if (it == dp.end())
                dp.push_back(nums[i]);
            else
                *it = nums[i];
        }
        return dp.size();
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值