1. 题目描述
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;
}
};