Acing 896. 最长上升子序列Ⅱ


<线性Dp / 贪心>


优化原因:

数据范围大较大若继续采用 O ( n 2 ) O(n^2) O(n2) 的朴素版做法会TLE, 故需采取优化


优化方案:

此处采取二分查找 O ( l o g n ) O(logn) O(logn)来优化其中一步 O ( n ) O(n) O(n)的遍历,从而整个代码由:
O ( n 2 ) O(n^2) O(n2) —> O ( n l o g n ) O(nlogn) O(nlogn)


分析思路如下:

因为需要求的是最长上升子序列,故末尾的值越小越好(小值的适应性会更好)

如:有以下六个数
a[0] a[1] a[2] a[3] a[4] a[5]
  5   3     2 	 3    4    6
若让5为末尾话,能接在5后面的数(a[i] > 5)必定能接在3(a[1])后面(a[i]肯定 > 3),
	而能接在3(a[1])后面的数(a[i] > 3),并不一定能接在5后面(a[i]不一定 > 5),如此处的4(a[4]);
同理得能接在3(a[1])后面的必定能接在2后面,
	而能接在2后面的不一定能接在3(a[1])后面,如此处的3(a[3])。

所以每次在接入a[i]时,在下标为0~len的区间内找到满足小于a[i]的最后一个数q[x](由此可知此处采取二分的第二个模板),并在其后面接入a[i],若找不到则将q[1]更新为该值,以免后面出现可以接在次a[i]后的更长的上升子序列


代码 + 详细注释:

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10;

int n;
int a[N], q[N]; // a[x]存放的是序列第x + 1个数的值,q[x]存放的是目前最长子序列中长度为x时的最优值(即最小值)

int main() {
    scanf("%d", &n);
    
    for (int i = 0; i <= n; ++ i) scanf("%d", a + i);
    
    int len = 0; // 目前的最大子序列长度
    for (int i = 0; i < n; ++ i) { // 遍历整个序列
        int l = 0, r = len; // 设立二分的初始边界
        /*(此处的l = 0是因为要防止二分找不到满足的值的情况,即此时a[i]为最小值,仍然需要将a[i]放进q[]数组<即放在q[1]>来更新后面未更新的原序列,以防会有更优解<即接在该a[i]后可能存在更长的上升子序列>)举例说明我就放在代码结束后啦,免得太过冗长~  此处为"说明1" */
        
        while (l < r) { // 开始采用第二个二分模板进行二分
            int mid = l + r + 1 >> 1;
            if (q[mid] < a[i]) l = mid; // 即找到小于a[i]的最后一个q[mid]
            else r = mid - 1;
        }
        len = max(len, r + 1); // 将序列长度更新为最大值(即每次更新为已有的最大上升子序列的长度和二分后的最大上升子序列的长度的最大值)
        q[r + 1] = a[i]; 
    /* 将a[i]接入找到的q[r]后,即q[r + 1](若没找到,即q[1] = a[i],即说明此时a[i]为最小值了,故将q[1]更新为目前的a[i],因为正如前面所说的越小的值越好,但要注意的是这里的q[]数组存放的并不一定是最终的最长子序列的结果,同上,举例放在后面啦~此处为"说明2") */
    }
    
    printf ("%d\n", len); // 输出结果(最长子序列的长度)
    
    return 0; // 结束快乐~
}

此处补上代码中提到的举例说明:

1. 说明1

说明1
如有以下数据:

4 5 1 2 3

第一次: len = 1, q[1] = 4
第二次: len = 2, q[2] = 5
第三次: len = 2, q[1] = 1
第四次; len = 2, q[2] = 2
第五次: len = 3, q[3] = 3

只有l从0开始,即l = 0,这样才能在最后二分找不到满足值的情况使l = r = 0,即最后r + 1 = 1,才能使q[r + 1] = a[i]满足我们需要的q[1] = a[i]来更新后面可能的最优解~~ 不然如果采取正常的l = 1开始,最后二分找不到时会是l = r = 1,然后r + 1 = 2,就无法满足我们需要的q[r + 1] = q[1] = a[i],故此处二分的左边界采用了一个看似用不到的l = 0来二分,实则是必须的~~


2. 说明2

说明2
如有以下数据:

2 3 1 4
第一次:len = 1, q[1] = 2
第二次:len = 2, q[2] = 3
第三次:len = 2, q[1] = 1
第四次:len = 3, q[3] = 4

故最后得到的最长子序列应为:
2 3 4
而最后的q[]数组为:
1 3 4

所以要得到最后的最长子序列的话,还需再开一个数组来记录每个数被放在了哪个数的后面,而不能通过q[]数组来得到~

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值