最长上升子序列解题报告
给定一个长度为N的数列(w[N]),求数值严格单调递增的子序列的长度最长是多少。
样例
输入格式
第一行包含整数N。
第二行包含N个整数,表示完整序列。
输出格式
输出一个整数,表示最大长度。
数据范围
1 ≤ N ≤ 1000,
−1e9 ≤ 数列中的数 ≤ 1e9
输入样例:
7
3 1 2 1 8 5 6
输出样例:
4
算法1
(动态规划) $O(n^2)$
-
状态表示:
f[i]
表示从第一个数字开始算,以w[i]结尾的最大的上升序列。(以w[i]
结尾的所有上升序列中属性为最大值的那一个) -
状态计算(集合划分):j∈(0,1,2,..,i-1), 在w[i] > w[j]时,
f[i] = max(f[i], f[j] + 1)。
有一个边界,若前面没有比i小的,f[i]为1(自己为结尾)。 -
最后在找
f[i]
的最大值。
时间复杂度
- $O(n^2)$ 状态数($n$) * 转移数($n$)
C++ 代码
#include <iostream>
using namespace std;
const int N = 1010;
int n;
int w[N], f[N];
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> w[i];
int mx = 1; // 找出所计算的f[i]之中的最大值,边算边找
for (int i = 0; i < n; i++) {
f[i] = 1; // 设f[i]默认为1,找不到前面数字小于自己的时候就为1
for (int j = 0; j < i; j++) {
if (w[i] > w[j]) f[i] = max(f[i], f[j] + 1); // 前一个小于自己的数结尾的最大上升子序列加上自己,即+1
}
mx = max(mx, f[i]);
}
cout << mx << endl;
return 0;
}
算法2
(动态规划 + 二分) $O(nlogn)$
-
状态表示:
f[i]
表示长度为i的最长上升子序列,末尾最小的数字。(长度为i的最长上升子序列所有结尾中,结尾最小min的) 即长度为i的子序列末尾最小元素是什么。 -
状态计算:对于每一个
w[i]
, 如果大于f[cnt-1]
(下标从0开始,cnt长度的最长上升子序列,末尾最小的数字),那就cnt+1,使得最长上升序列长度+1,当前末尾最小元素为w[i]
。 若w[i]
小于等于f[cnt-1]
,说明不会更新当前的长度,但之前末尾的最小元素要发生变化,找到第一个 大于或等于 (这里不能是大于) w[i],更新以那时候末尾的最小元素。 -
f[i]一定以一个单调递增的数组,所以可以用二分法来找第一个大于或等于w[i]的数字。
时间复杂度
- $O(nlogn)$ 状态数($n$) * 转移数($logn$)
C++ 代码
#include <iostream>
using namespace std;
const int N = 1010;
int n, cnt;
int w[N], f[N];
int main() {
cin >> n;
for (int i = 0 ; i < n; i++) cin >> w[i];
f[cnt++] = w[0];
for (int i = 1; i < n; i++) {
if (w[i] > f[cnt-1]) f[cnt++] = w[i];
else {
int l = 0, r = cnt - 1;
while (l < r) {
int mid = (l + r) >> 1;
if (f[mid] >= w[i]) r = mid;
else l = mid + 1;
}
f[r] = w[i];
}
}
cout << cnt << endl;
return 0;
}
状态表示那里写错了把,f[i] 表示的是长度为i + 1单增子序列末尾最小值
题解二,如果子序列长度相同, 那么最末尾元素较小比较有优势
从数列首开始,如果a[i] > f[ans - 1] 则 f[ans ++] = a[i];
否则从f数组首开始找,找到第一个大于a[i]的f[x],更新,f[x]=a[i];
最后答案就是ans;
但是我dp做得少,怎么感觉这个解更像是贪心而不是dp,因为答案是f[]的下标感觉怪怪的…
这保存不了路径啊!!
两种好像都可以....
大佬们看看哪里出错了
正确代码
“#include [HTML_REMOVED]
#include [HTML_REMOVED]
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N], q[N];
int main(){
cin >> n;
for(int i = 0; i < n; i ++) cin >> a[i];
}
“
算法二为什么这样操作就可以找到最长上升子序列呢?
行
写的很好
为什么样例结果是4啊?
这个应该不要求连续,例子给的应该是1,2,5,6。
算法1中当aj<ai时的式子是什么样的呢
大佬好厉害
若w[i]小于等于f[cnt-1],说明不会更新当前的长度,但之前末尾的最小元素要发生变化,找到第一个 大于或等于 (这里不能是大于) w[i],更新以那时候末尾的最小元素
这里为什么大于等于?有没有通俗的理解方式
可能是笔误,我都理解是不能是等于,如果等于就没有更新的必要,只要找到比当前w[i]还要大的才回去更新前一步的f[cnt-1]作为它的末尾最小元素
我当时也是这么想的,但第二个代码二分时如果把if (f[mid] >= w[i]) r = mid;里面的>=换成>,就会报错 想不通为什么,就不了了之了
把正确的答案往r靠,最后正确答案会在r上
就比如例题里121,假设f[1]=1,f[2]=2,此时1<2,二分,如果fmid大于wi,就会找到第一个大于1的数,f[2]就会等于1,f就不会单调递增
写的很好
asdfasdfasdf;alkdjfa;ldkfa;dsklf j
dfasdf
还是不得不感叹 太妙了
请问,如果我想让f[i]表示的是前i个数的最长上升子序列长度,应该如何改代码呢,我感觉这样考虑好理解一些
%%%%
for (int j = 0; j < i; j++) 这里为啥 j 的范围写成 <n 也能过啊 为什么 j 一定小于 i 。
因为她之前把f[i]=1了,这里就包含了i自己成为一个序列,j小于i的原因就是更新前面f[j]的值,然后去更新后面f[j]的值
if (f[mid] >= w[i]) r = mid; 为什么这里要等于啊不是严格递增吗
这个是模板,不会变的,你说的严格单调递增表示在if (w[i] > f[cnt-1]) 这里了
透彻
第二个 豁然开朗