LIS O(n log n) 详解

来源:互联网 发布:淘宝优惠券在哪领 编辑:程序博客网 时间:2024/05/20 22:28

虽然之前就会了,但是今天用这个方法来求 O(n ^ 2) 的 LIS 中用到的 f[i] 时出了点小差错,所以特立此贴,留作教训。

1.传统 O(n ^ 2) LIS
用 DP 的原理。设 f[i] 为 以第i个元素结尾的上升子序列的最大长度。易得状态转移方程 f[i] = max{ f[j] + 1 | num[j] < num[i], j < i}。

2.O(n log n) LIS
设h[i]为 长度为 i 的上升子序列的结尾元素的最小值。很明显,这个 h[i] 是个单调递增的数组。
证明:如果h[i + 1] < h[i],那么长度为 i 的子序列可以索性不要 h[i] 这个元素,而是让 h[i + 1] 结尾,所以 h[i] 不会大于 h[i + 1]。
如果有h[i] = h[i + 1],考虑长度为 i + 1的子序列是如何生成的。它必然是由长度为 i 的子序列加上一个更大的数组成的。现在长度为 i 的子序列的结尾的最小值为 h[i],那么长度为 i + 1 的子序列的结尾不可能等于 h[i],否则子序列就不是严格上升的了。
因此 h[i] 是个单调递增的数组。

怎么求最长上升子序列的长度呢?
很明显,如果我们按正确的算法操作下去,最后的答案就是 数组h 的大小。那怎么操作呢?
如果 h.back() < num,就把 num 加入 h 的末尾(push_back);否则在 h 中二分查找,找到第一个大于等于 num 的数(lower_bound),把这个数改成 num。只要知道了 h[i] 的含义,这个操作应该就不难理解。

3.用 2 的方法求 1 的 f 数组
如果我们要求 f 数组,也可以用 2 的方法来求。如果 h.back() < num,那么 f[i] 就等于 push_back(num) 之后的 h.size()。如果 h.back() >= num,设二分查找找到的 iterator 为 it,那么 f[i] = it - h.begin() + 1。

4.注意
如果是严格上升下降,应当用 lower_bound,否则用 upper_bound。同时注意把 num push_back 到 h 中时到底要求满足的是 小于 还是 小于等于。

原创粉丝点击