给定长度为$N$的序列$a_i$,求序列中一个长度最长且递增的子序列。输出子序列的长度。$N\leq 1000$。
$O(N^2)$解法
设$f[i]$为以$a_i$结尾的最长上升子序列的长度
易知$f[i]$的最大值为 区间$[1,i)$中的 小于$a_i$的最大的$j$加上1
意思就是,
假设$a_j$为区间$[1,i)$中小于$a_i$的最大值,那么$a_i$就可以拼在$a_j$的后面,这时以$a_i$结尾的LIS的长度就为以$a_j$结尾的LIS长度+1
用状态转移方程表示就是
$\large{f[i]=\displaystyle\max_{j=1}^{i-1}(f[j])+1,a_i>a_j}$
用代码表示就是
1
2
3
4
5
6
7
8
9
10int A[N+1];
int f[N+1];
for(int i = 1;i<=N;i++){
for(int j = 1;j<i;j++){
if(A[i] <= A[j]) continue;
f[i] = max(f[i],f[j]+1);
}
}
int max_ = -1;
for(int i = 1;i<=N;i++) max_ = max(f[i],max_);
$O(N\log N)$解法
设$f[i]$为长度为$i$的上升子序列的最后一个数字
设$a_j$为序列的第$j$个元素
若$f{i}\leq a_j < f{i+1}$,则将$f_i$的值更新为$a_j$
相当于将长度为$i$的上升子序列的最后一位$f_i$改为了更小的$a_j$,显然这样更有利于在后面接其他元素
但是目前遍历长为$n$的序列,对于其中每项$a_j$,都需要进行至多$i$次操作去寻找合适的区间,时间复杂度仍然为$O(N^2)$
但是,由于$f_i$单调递增,可以在查找的时候使用二分查找来优化,这样就至多进行$\log N$次查找,时间复杂度降为$O(N\log N)$
代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17int A[N+1];
int f[N+1];
int cnt = -1;
//cnt代表当前最长上升子序列的长度
fill(f+1,f+N+1,1e9);
//fill(st,ed,val)意为用val填充数组[st,ed)区间
//使用极大值填充区间
for(int i = 1;i<=N;i++){
int j = lower_bound(f+1,f+N+1,A[i]) - f;
//lower_bound(st,ed,val)意为在[st,ed)区间内查找第一个不小于val的元素
//lower_bound返回一个指向找到元素的迭代器
//减去的f是地址
//地址 - 地址 = 下标
cnt = max(cnt,j);
f[j] = A[i];
}
int ans = cnt;