LeetCode354 -- 最长上升子序列
1. 题目描述
2. 思路
非常明显的上升子序列问题。但是我在做的时候遇到了一个之前做 \(LCS\) 从来没考虑过的点。
之前都是直接排序,而无论是左端点优先还是右端点优先,假设左端点优先吧并且一般都是升序,我们一般是让右端点也升序的。
也就是说,左右端点都是升序的。做了很多 \(LCS\),都没有问题,直到这里,由于一些机缘巧合,\(bug\) 来了!
看看下面的例子:\([1,10], [2,4], [3,5]\)
我在做的时候,因为数据规模在 \(10^{5}\), 所以使用了贪心的做法,按照左端点升序,然后右端点升序。
然后二分查找合适的长度并更新答案。
然后对于每个长度,保存一个 \([left,right]\),即 q[len] = {left, right}
,表示长度为 \(k\) 时,左右端点的最小值。(注意⚠️,这句话问题很大!)
对于该例子,我们希望输出长度 \(2\),即,\([2,4], [3,5]\) 显然是符合条件的。
但是我的程序输出了 \(1\),为什么呢?
原来是,长度为 \(1\) 时,我们选择了 \([1, 10]\),而遍历到 \([2,4]\) 时,\(4 < 10\),因此跳过去了,\([3,5]\) 也是。
注意到问题所在了没有?对于 \([1,10]\) 和 \([2,4]\),谁“更小”?
答案是不知道或者说是不确定🤔。
对于该例子,显然 \([2,4]\) 更合适,好像乍看起来,如果后面的数的右端点更小的话,总是更合适啊?
别急,看下面的例子:\([3,10], [12,1], [12,11], [13,20]\)
长度为 \(1\) 时,应该选择 \([3,10]\) 而不是 \([12,1]\)。啊,麻烦了!
我们发现,我们无法确定谁更好,到底是选择左端点小的呢?还是右端点小的呢?不知道,除非我们知道后面的数据是什么。
但是显然我们不知道后面的数据。
这时候,就要拷问前面的⚠️了!对于每个长度,我们保存了两个信息,这就会导致歧义,到底是第一个信息(\(left\))的优先级高?还是第二个信息 (\(right\))的优先级高呢?
不知道!
但是如果我们只保存了一个信息的话,那么就可以准确的确定优先级了(不会有歧义)!
因为我们按照左端点排序的,所以左端点肯定是升序(⚠️不严格升序)的,出于贪心的角度,我们肯定希望右端点越小越好,这样后面的区间就更容易合法。
那么我们就只保存右端点即可,即 q[len] = right
好像大功告成了?\(NO\)!
对于这个例子,\([3,5], [12,7], [12,11], [13,20]\),如果我们按照上面的思路,结果会是 \(4\) 而不是 \(3\)。
因为我们会把 \([12,7], [12,11]\) 都算作答案,你可能会问,你在比较一下左端点不就行了吗?
但是我们的 q[len]
只保存了右端点啊,如果你想要比较左端点,你就需要再保存它,那不就会到上面的讨论了吗?
怎么办呢?
答案是,对于右端点,我们不能升序,而是降序,将序列转化为:
\([3,5], [12,11], [12,7], [13,20]\),那么由于 \([12,7]\) 的右端点小于 \([12,11]\) 的右端点,所以就不会选它了。
这样就可以完全避免左端点相等时多次选取的情况,因为如果左端点相等,那么右端点大的总是先出现,它先出现就会导致下一个区间的右端点匹配失败,
你可能会问,那不就是让右端点每次选取最大值了吗!\(oh\),我们一开始就提到过了,有时候右端点越小越好啊!
且慢!
在 \([3,5], [12,7], [12,11], [13,20]\) 中,当我们遍历到 \([12,11]\) 时,它更新的是 q[3]
也就是长度为 \(3\) 的情况。
而在 \([3,5], [12,11], [12,7], [13,20]\) 中,它更新的是 q[2]
也就是长度为 \(2\) 的情况,此时 q[2]=11 -> q[2]=7
,成功替换为了更小的值!
看出来区别了吧!
总之,意会吧!
最后提一点,如果我们没有使用贪心优化的做法, 而是使用暴力 \(O(n^2)\) 的话,是不需要考虑这些的,直接全部升序排就行了!
因为此时对于左右端点的信息,我们可以轻松得知!
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;
}
};