LeetCode354 -- 最长上升子序列

1. 题目描述

354. 俄罗斯套娃信封问题



2. 思路

非常明显的上升子序列问题。但是我在做的时候遇到了一个之前做 L C S LCS LCS 从来没考虑过的点。
之前都是直接排序,而无论是左端点优先还是右端点优先,假设左端点优先吧并且一般都是升序,我们一般是让右端点也升序的。
也就是说,左右端点都是升序的。做了很多 L C S LCS LCS,都没有问题,直到这里,由于一些机缘巧合, b u g bug bug 来了!

看看下面的例子: [ 1 , 10 ] , [ 2 , 4 ] , [ 3 , 5 ] [1,10], [2,4], [3,5] [1,10],[2,4],[3,5]
我在做的时候,因为数据规模在 1 0 5 10^{5} 105, 所以使用了贪心的做法,按照左端点升序,然后右端点升序。
然后二分查找合适的长度并更新答案。
然后对于每个长度,保存一个 [ l e f t , r i g h t ] [left,right] [left,right],即 q[len] = {left, right},表示长度为 k k k 时,左右端点的最小值。(注意⚠️,这句话问题很大!)
对于该例子,我们希望输出长度 2 2 2,即, [ 2 , 4 ] , [ 3 , 5 ] [2,4], [3,5] [2,4],[3,5] 显然是符合条件的。
但是我的程序输出了 1 1 1,为什么呢?
原来是,长度为 1 1 1 时,我们选择了 [ 1 , 10 ] [1, 10] [1,10],而遍历到 [ 2 , 4 ] [2,4] [2,4] 时, 4 < 10 4 < 10 4<10,因此跳过去了, [ 3 , 5 ] [3,5] [3,5] 也是。
注意到问题所在了没有?对于 [ 1 , 10 ] [1,10] [1,10] [ 2 , 4 ] [2,4] [2,4],谁“更小”?
答案是不知道或者说是不确定🤔。

对于该例子,显然 [ 2 , 4 ] [2,4] [2,4] 更合适,好像乍看起来,如果后面的数的右端点更小的话,总是更合适啊?
别急,看下面的例子: [ 3 , 10 ] , [ 12 , 1 ] , [ 12 , 11 ] , [ 13 , 20 ] [3,10], [12,1], [12,11], [13,20] [3,10],[12,1],[12,11],[13,20]
长度为 1 1 1 时,应该选择 [ 3 , 10 ] [3,10] [3,10] 而不是 [ 12 , 1 ] [12,1] [12,1]。啊,麻烦了!
我们发现,我们无法确定谁更好,到底是选择左端点小的呢?还是右端点小的呢?不知道,除非我们知道后面的数据是什么。
但是显然我们不知道后面的数据。

这时候,就要拷问前面的⚠️了!对于每个长度,我们保存了两个信息,这就会导致歧义,到底是第一个信息( l e f t left left)的优先级高?还是第二个信息 ( r i g h t right right)的优先级高呢?
不知道!
但是如果我们只保存了一个信息的话,那么就可以准确的确定优先级了(不会有歧义)!
因为我们按照左端点排序的,所以左端点肯定是升序(⚠️不严格升序)的,出于贪心的角度,我们肯定希望右端点越小越好,这样后面的区间就更容易合法。
那么我们就只保存右端点即可,即 q[len] = right

好像大功告成了? N O NO NO
对于这个例子, [ 3 , 5 ] , [ 12 , 7 ] , [ 12 , 11 ] , [ 13 , 20 ] [3,5], [12,7], [12,11], [13,20] [3,5],[12,7],[12,11],[13,20],如果我们按照上面的思路,结果会是 4 4 4 而不是 3 3 3
因为我们会把 [ 12 , 7 ] , [ 12 , 11 ] [12,7], [12,11] [12,7],[12,11] 都算作答案,你可能会问,你在比较一下左端点不就行了吗?
但是我们的 q[len] 只保存了右端点啊,如果你想要比较左端点,你就需要再保存它,那不就会到上面的讨论了吗?
怎么办呢?
答案是,对于右端点,我们不能升序,而是降序,将序列转化为:
[ 3 , 5 ] , [ 12 , 11 ] , [ 12 , 7 ] , [ 13 , 20 ] [3,5], [12,11], [12,7], [13,20] [3,5],[12,11],[12,7],[13,20],那么由于 [ 12 , 7 ] [12,7] [12,7] 的右端点小于 [ 12 , 11 ] [12,11] [12,11] 的右端点,所以就不会选它了。
这样就可以完全避免左端点相等时多次选取的情况,因为如果左端点相等,那么右端点大的总是先出现,它先出现就会导致下一个区间的右端点匹配失败,

你可能会问,那不就是让右端点每次选取最大值了吗! o h oh oh,我们一开始就提到过了,有时候右端点越小越好啊!
且慢!
[ 3 , 5 ] , [ 12 , 7 ] , [ 12 , 11 ] , [ 13 , 20 ] [3,5], [12,7], [12,11], [13,20] [3,5],[12,7],[12,11],[13,20] 中,当我们遍历到 [ 12 , 11 ] [12,11] [12,11] 时,它更新的是 q[3] 也就是长度为 3 3 3 的情况。
而在 [ 3 , 5 ] , [ 12 , 11 ] , [ 12 , 7 ] , [ 13 , 20 ] [3,5], [12,11], [12,7], [13,20] [3,5],[12,11],[12,7],[13,20] 中,它更新的是 q[2] 也就是长度为 2 2 2 的情况,此时 q[2]=11 -> q[2]=7,成功替换为了更小的值!

看出来区别了吧!
总之,意会吧!

最后提一点,如果我们没有使用贪心优化的做法, 而是使用暴力 O ( n 2 ) O(n^2) O(n2) 的话,是不需要考虑这些的,直接全部升序排就行了!
因为此时对于左右端点的信息,我们可以轻松得知!



3. 代码

下面代码,我是按照左端点升序,右端点降序排序的,然后 q[] 保存右端点的最小值。
我们也可以右端点升序,左端点降序排序的,然后 q[] 保存左端点的最小值。

class Solution {
public:
    int maxEnvelopes(vector<vector<int>>& g) {
        int n = g.size();
        sort(g.begin(), g.end(), [&](auto &x, auto &y){
            if(x[0] == y[0])    return x[1] > y[1]; // ⚠️
            return x[0] < y[0];
        });

        vector<int> q(n + 10);
        int len = 0;
        for(auto &x : g) {
            int l = 0, r = len;
            while(l < r) {
                int mid = l + r + 1 >> 1;
                if(x[1] > q[mid])   l = mid;
                else r = mid - 1;
            }
            len = max(len, l + 1);
            q[l + 1] = x[1];    // 一定更小
        }
        return len;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值