解题思路

传统的动态规划思想是,维护一个dp数组,dp[k] 表示以元素 arr[k] 为末尾的最长严格上升子序列的长度。

但是这个方法要遍历 k 以前的所有位置,找到满足arr[i] < arr[k]且dp[i]最大的那一个,令dp[k] = dp[i] + 1。

时间复杂度为O(n²),这个时间复杂度说实话真的不那么令人满意,有没有更优秀的解法呢?

答案是有的。

根据贪心的思想,如果我维护的子序列上升的速度越慢,那么它就可以加入更多的元素。

比如原始序列 { 1, 4, 6, 2, 3, 5, 8, 7 },其中子序列 {1, 4, 6 } 比 { 1, 2, 3 } 上升的更快,这两种情况下我们选择 { 1, 2, 3 } 来让后面上升的空间更大。

这样一来,问题就转化成了如何去找到这样一个上升最慢的序列~

具体做法是这样子的:

创建一个动态数组,命名为p。然后遍历arr的每一个元素。对于arr[i],若该它比p的最后一个元素还大,就将它添加到p的末尾;否则找到p中第一个大于等于它的元素,用arr[i]替换掉这个元素。

当arr遍历完后,数组p的长度就是答案。

单纯的文字可能不容易理解,下面用一个例子具体说明。arr数组为: { 1, 4, 6, 2, 3, 5, 8, 7 }

1、初始p = { }

2、arr[0] = 1,于是p = { 1 }。

3、arr[1] = 4,于是p = { 1, 4 }。

4、arr[2] = 6,于是p = { 1, 4, 6 }。

5、arr[3] = 2,用2替换4,于是p = { 1, 2, 6 }。

6、arr[4] = 3,用3替换6,于是p = { 1, 2, 3 }。

7、arr[5] = 5,于是p = { 1, 2, 3, 5 }。

8、arr[6] = 8,于是p = { 1, 2, 3, 5, 8 }。

9、arr[7] = 7,用7替换8,于是p = { 1, 2, 3, 5, 7 }。

最终答案为p的长度5。

由于p是严格上升的,因此查找p中第一个大于等于arr[i]的元素,可以使用二分查找,时间复杂度为O(log n)。

这样一来整体的时间复杂度就是O(n log n),比O(n²)要优秀得多。

下面贴出代码。

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n;
    cin >> n;
    vector<int> arr(n);
    for(int &i : arr) {
        cin >> i;
    }
    vector<int> p;
    p.push_back(arr[0]);
    for(int i = 1; i < n; ++i) {
        if(arr[i] > p.back()) {
            p.push_back(arr[i]);
        } else {
            auto pos = lower_bound(p.begin(), p.end(), arr[i]) - p.begin();
            p[pos] = arr[i];
        }
    }
    cout << p.size();
}