最长上升子序列&&最长不下降子序列
本文将用二种方法来求各种子序列,但详细介绍求最长上升子序列,其他子序列思路相同,代码也几乎不变。
求最长上升子序列
方法1:
思路:用f[i]来表示序列从 起点 开始到 i 点,最长上升子序列的长度;
所以f[i]初始化为1,因为当前面没有比他小的点时,子序列只有 i点一个点。
当从头开始遍历到 i点,如果在中间碰到比 i点 小的点 (假设为j点),就进行状态转移方程:
f[i] = max(f[i],f[j]+1);
此状态转移方程的意思,我的理解,就是比较将i点放入 j点代表的最长上升子序列长度+1和原本自身的f[i]进行相比,取最大值;
用 {8 4 2 5 3 9
}这个数组进行演示说明(下标从1开始);
//8 4 2 5 3 9 //最开始 i= 1; //f[1] = 1; //a[1] = 8; //由于8之前没有比他小的数,f[1]保持不变 //i =2; //f[2] =1; //a[2] =4; //由于在4之前没有比他小的点,f[2]保持1不变 //i =3; //f[3] = 1; //a[3] =2; //由于在2之前没有比他小的点,f[3]保持不变 //i = 4; //f[4] = 1; //a[4] =5; //5之前比他小的点有 2 ,由于f[2] =1;则f[4] = max(f[3]+1,f[4]) = 2; //i = 5; //f[5] =1; //a[5] =3; //3之前比他小的点有2,由于f[2] = 1;则f[5] =max(f[3]+1,f[5]) = 2; //i = 6; //f[6] = 1; //a[6] =9; //9之前比他小的点有8,4,2,5,3,从8开始,由于f[1] =1 ,则 f[6] =max(f[1]+1,f[6]) = 2; // 到4,由于f[2] = 1,则 f[6] =max(f[2]+1,f[6]) = 2; // 到2,由于f[3] = 1,则 f[6] =max(f[3]+1,f[6]) = 2; // 到5,由于f[4] = 2,则 f[6] =max(f[4]+1,f[6]) = 3;这一步就是在说明f[j] +1 不一定就是最终结果 // 到3,由于f[5] = 2,则 f[6] =max(f[5]+1,f[6]) = 3;
代码如下:
#include<bits/stdc++.h> using namespace std; int n; int a[100005],f[100005]; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); } int ans = 0; for(int i=1;i<=n;i++) { f[i] =1; for(int j=1;j<i;j++) { if(a[i]>a[j]) { f[i] = max(f[i],f[j]+1); } ans = max(f[i],ans); } } cout<<ans<<endl; }
方法2:
思路:这次的f[i]数组不再储存每个点的最长子数列,而是长度为i+1的上升子序列中末尾元素的最小值
只将数组遍历一次即可:碰到比f[i] 的数,就放在后面,i++。当碰到比f[i]小的数,就遍历f[]数组,找出第一个比他小的数,并替换掉。
附:介绍一种可以快速查找数组中数大小的办法:
lower_bound( )和upper_bound( )都是利用二分查找的方法在一个排好序的数组中进行查找的。
lower_bound():返回的是被查序列中第一个大于等于查找值的指针
upper_bound():返回的是被查序列中第一个大于查找值得指针
代码如下:
#include<bits/stdc++.h> using namespace std; int n; int a[100005],f[100005]; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); } f[1] = a[1]; int len = 1; for(int i=2;i<=n;i++) { if(a[i]>f[len]) { f[++len] = a[i]; }else{ int tmp = upper_bound(f+1,f+1+len,a[i]) - f; f[tmp] = a[i]; } } cout<<len<<endl; }