线性dp:最长上升子序列
最长上升子序列
- 本文与leetcode300.最长递增子序列,这题题意一样,阅读完本文可以挑战一下
题目叙述:
给定一个无序的整数数组,找出其中最长上升子序列(LIS)的长度。
输入:
[5,7,1,9,4,6,2,8,3]
输出
4
解释
- 最长上升子序列是[1,4,6,8],其长度为4。
动态规划的设计:
-
首先,我们对数组进行下标的映射,我们从下标为1的位置开始计数
-
并且,我们计算以每一个元素a【i】为结尾的最长上升子序列的长度f【i】。
- 由上面这张图我们可知,最长的上升子序列可以由前面的状态推出,因此我们可以考虑使用动态规划算法
状态变量dp的含义:
- 我们在这里数组名用f,而不用dp
f[i]代表以a[i]为结尾的最长上升子序列长度
- 初始条件为f[i]=1
递推公式:
- 我们需要一个j指针,每次遍历到a【i】时,我们都需要j指针,从1开始移动,移动到i-1的位置,如果发现a[i]>a[j],那么我们就更新f【i】的值,不过还得判断一下f【j】+1与f【i】的大小关系。
for(int i=2;i<=n;i++){
for(int j=1;j<i;j++){
if(a[i]>a[j]) f[i]=max(f[i],f[j]+1);
}
//ans为最终的最大上升子序列的长度
ans=max(ans,f[i]);
}
遍历顺序:
- 因为我们的状态变量
f[i]
是由f[j]
决定的,所以说我们的遍历顺序显然是从前向后遍历
如何初始化?
- 在上面已经说了:
f[i]=1
,因为每个数字至少都有以本身为序列的最长上升子序列。
举例打印dp数组
-
下标 :1,2,3,4,5,6,7,8,9
-
a【i】:5,7,1,9,4,6,2,8,3
-
f【i】 :1,2,1,3,2,3,2,4,3
递推式满足的条件
- 由小推大(最优子结构)
- 由过去推现在(无后效性)
疑问:
- f【i】记录以a【i】为开头的最长上升子序列可以吗?——可以,不过遍历顺序就是从后向前遍历,递推式也需要改变改变,这里读者可以自行推理!
- f【i】记录前i个数的最长上升子序列的长度,可以吗?——不可以,举例两个就可以发现明显错误的反例,因此不成立。
代码实现:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1010;
int n, a[N];
int f[N];
int main(){
cin>>n;
for(int i=1; i<=n; i++) cin>>a[i];
for(int i=1; i<=n; i++) f[i]=1;
for(int i=1; i<=n; i++)
for(int j=1; j<i; j++)
if(a[j]<a[i]) f[i]=max(f[i],f[j]+1);
int res=0;
for(int i=1; i<=n; i++) res=max(res,f[i]);
cout<<res;
}