[AcWing] 896. 最长上升子序列 II(C++实现)线性dp例题 思想更接近贪心
1. 题目
2. 读题(需要重点注意的东西)
思路:
dp求解上升子序列的问题:
相比于最长上升子序列问题,增大了数据N的范围到1e5,最长上升子序列问题的时间复杂度为O(n^2),因此,需要考虑一些优化,来降低时间复杂度
优化方法:
对于每个长度的上升子序列,我们用二分找出数组q中比a[i]小的最大的数(找出来是q中的下标r),然后用这个值更新q[r+1]和len。
这样,q[i] 存储的就是 序列长度为 i 的结尾值最小的元素
时间复杂度:
二分O(logn),更新O(1),最终的时间复杂度是O(logn)
整体流程如下:
3. 解法
---------------------------------------------------解法---------------------------------------------------
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
int a[N]; // 存储数列
int q[N]; // 存储子序列长度结尾元素的最小值
/*
如q[2],表示上升序列长度为2时,结尾元素最小值为q[2]
即若有子序列 1 2 和子序列 1 5;
因为2 < 5,所以q[2] = 2
*/
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
int len = 0; // 前i个数最大的上升子序列的值
/* 枚举每个数a[i],做如下操作
1. 二分出数组q中小于a[i]的最大的数,因为要将a[i]插到该序列之后
2. 更新前i个数最大的上升子序列的值:len = max(len, r + 1);
3. 将a[i]插到序列长度为r的后面,更新q:q[r + 1] = a[i];
*/
for (int i = 0; i < n; i ++ )
{
// 二分出小于a[i]的最大的数
int l = 0, r = len;
while (l < r)
{
int mid = l + r + 1 >> 1;
if (q[mid] < a[i]) l = mid;
else r = mid - 1;
}
// 更新前i个数最大的上升子序列的值
len = max(len, r + 1);
// 更新q
q[r + 1] = a[i];
}
printf("%d\n", len);
return 0;
}
可能存在的问题
4. 可能有帮助的前置习题
5. 所用到的数据结构与算法思想
- 动态规划
- 贪心
- 二分
6. 总结
线性dp的例题,思想更接近贪心,理解思想并自己写出代码。