最长上升子序列
问题
已知一个序列,求其子序列为上升子序列的最大值
分析
我们发现这个题,如果我们直接暴力也是可以出结果的
但是暴力解决不了问题,所以我们还是得找方法
关于暴力就不多说了
我们来看看动态规划(动态规划需要最优子结构和重叠子问题)
上课的时候,我一直没懂为啥要用一个序列里面的第i位来作为子序列中最后一位。
我最开始就想的是求啥设啥,要求长度,我们就把长度作为规划目标
这是不对的!!
比如:
这时候是不是就很神奇
我们发现如果直接把长度作为目标,他就没有最优子结构性质
所以,我们需要引入一些中间目标作为我们的对象
最大限界上升子序列
所以我们用一个序列里面的第i位来作为子序列中最后一位来求取第i位为末尾的序列中的最大上升值
实现
先来看看时间复杂度为n^2的方法
用dp数组来记录第i个位置时最大上升子序列的长度
先比较在i前面的元素是否小于i
当小于i时再比较这个元素的长度+1与i的长度的关系
如序列:1 3 4 2 7 9 6 8
其对应的长度为1 2 3 2 4 5 4 5
代码
#include<stdio.h>
#include<string.h>
int main()
{
int n,i,j;
int arr[100],dp[100];
scanf("%d",&n);
for(i=1;i<=n;i++){
scanf("%d",&arr[i]);
dp[i]=1;
}
int len=1;
for(i=1;i<=n;i++){
for(j=1;j<i;j++){
if(arr[j]<arr[i]){
if(dp[i]<dp[j]+1){
dp[i]=dp[j]+1;
}
}
}
if(dp[i]>len) len=dp[i];
}
/*for(i=1;i<=n;i++)
printf("%d ",dp[i]);
*/
printf("%d\n",len);
return 0;
}
进一步优化为nlogn的方法
对于序列13427968
我们可通过二分进行查找判断
过程如图
代码
#include<stdio.h>
#include<string.h>
int arr[101];
int t[101];
int binary(int i,int k){
int m=1,n=k;
if(arr[i]<t[1]) return 1;
while(m!=n-1){
if(t[k=(m+n)/2]<=arr[i]) m=k;
else n=k;
}
return n;
}
int main()
{
int n,len=1,i,m;
scanf("%d",&n);
for(i=1;i<=n;i++){
scanf("%d",&arr[i]);
}
t[1]=arr[1];
for(m=2;m<=n;m++){//计算每个字符对应的最长值
if(arr[m]>=t[len]){
len=len+1;
t[len]=arr[m];
}else{
t[binary(m,len)]=arr[m];//t数组严格递增,所以二分查找
}
}
printf("%d\n",len);
return 0;
}
练习
题目描述:Alignment
大意:
有n个士兵,并给定其身高
求出去多少人后,使得剩余人的身高按三角状排列(即中间高,两边低)
那么这个题,我们需要正向寻找一次最长上升子序列,反向再寻找一次最大上升子序列,要求得两数相加最大(位置不能重叠)
=========================================================
先来看看我的代码
(有点麻烦,但也过了)
在反向处理的地方,我们知道逆向开始的元素必须大于正向结束元素,不然会出现重叠,所以这里处理先翻转数组得到逆向的最大上升,但这里的储存是逆序的,所以再次翻转
#include<stdio.h>
#include<string.h>
int main()
{
int n,i,j;
double arr[1010];
int dp1[1010],dp2[1010];
scanf("%d",&n);
for(i=1;i<=n;i++){
scanf("%lf",&arr[i]);
dp1[i]=1;
dp2[i]=1;
}
for(i=1;i<=n;i++){//正向上升
for(j=1;j<i;j++){
if(arr[j]<arr[i]){
if(dp1[i]<dp1[j]+1){
dp1[i]=dp1[j]+1;
}
}
}
}
for(i=1,j=n;i<=n/2;i++,j--){//翻转数组
double p=arr[i];
arr[i]=arr[j];
arr[j]=p;
}
for(i=1;i<=n;i++){//反向上升保存顺序反的
for(j=1;j<i;j++){
if(arr[j]<arr[i]){
if(dp2[i]<dp2[j]+1){
dp2[i]=dp2[j]+1;
}
}
}
}
for(i=1,j=n;i<=n/2;i++,j--){//再次翻转
int p=dp2[i];
dp2[i]=dp2[j];
dp2[j]=p;
}
int max=dp1[n];
for(i=1;i<n;i++){
for(j=i+1;j<=n;j++){
if(dp1[i]+dp2[j]>max) max=dp1[i]+dp2[j];
}
}
printf("%d\n",n-max);
return 0;
}
再来看看借鉴大佬的反向最大上升
#include<stdio.h>
#include<string.h>
int main()
{
int n,i,j;
double arr[1010];
int dp1[1010],dp2[1010];
scanf("%d",&n);
for(i=1;i<=n;i++){
scanf("%lf",&arr[i]);
dp1[i]=1;
dp2[i]=1;
}
for(i=1;i<=n;i++){//正向上升
for(j=1;j<i;j++){
if(arr[j]<arr[i]){
if(dp1[i]<dp1[j]+1){
dp1[i]=dp1[j]+1;
}
}
}
}
for(i=n-1;i>=1;i--){//反向上升
for(j=i+1;j<=n;j++){
if(arr[j]<arr[i]){
if(dp2[i]<dp2[j]+1){
dp2[i]=dp2[j]+1;
}
}
}
}
int max=dp1[n];
for(i=1;i<n;i++){
for(j=i+1;j<=n;j++){
if(dp1[i]+dp2[j]>max) max=dp1[i]+dp2[j];
}
}
printf("%d\n",n-max);
return 0;
}
====================================================
关于这个反向
自己也写了一个,但始终是WA,希望有大佬可以帮忙看看
for(i=n-1;i>=1;i--){//反向上升
for(j=n-1;j>i;j--){
if(arr[j]<arr[i]){
if(dp2[i]<dp2[j]+1){
dp2[i]=dp2[j]+1;
}
}
}
}
救救孩子叭!!!!