Almost Sorted Array
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Others)
Total Submission(s): 6642 Accepted Submission(s): 1586
Problem Description
We are all familiar with sorting algorithms: quick sort, merge sort, heap sort, insertion sort, selection sort, bubble sort, etc. But sometimes it is an overkill to use these algorithms for an almost sorted array.
We say an array is sorted if its elements are in non-decreasing order or non-increasing order. We say an array is almost sorted if we can remove exactly one element from it, and the remaining array is sorted. Now you are given an array a1,a2,…,an, is it almost sorted?
Input
The first line contains an integer T indicating the total number of test cases. Each test case starts with an integer n in one line, then one line with n integers a1,a2,…,an.
1≤T≤2000
2≤n≤105
1≤ai≤105
There are at most 20 test cases with n>1000.
Output
For each test case, please output “YES
” if it is almost sorted. Otherwise, output “NO
” (both without quotes).
Sample Input
3
3
2 1 7
3
3 2 1
5
3 1 4 1 5
Sample Output
YES
YES
NO
这道题可以模拟也能用最长递增子序列来写。
方法一:思维
#include<algorithm>
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=100005;
int n,m;
int as[2][maxn];
int main()
{
int t;
cin>>t;
while(t--)
{
int f1=0,f2=0,d,i,j1=0,j=0;
cin>>m;
scanf("%d",&as[0][0]);
as[1][0]=as[0][0];
for (i=1;i<m;i++)
{
scanf("%d",&d);
if (f1<=1) ///递减出现两次存放不了就不用执行了
{
if(d>as[0][j]) ///不满足递减
{
if ((j>0&&d<=as[0][j-1])||j==0) ///判断是否跟新最后一个值
as[0][j]=d;
f1++;
}
else as[0][++j]=d; ///满足则添加
}
if (f2<=1) ///递增同上
{
if(d<as[1][j1])
{
if (j1>0&&d>=as[1][j1-1]||j1==0)
as[1][j1]=d;
f2++;
}
else as[1][++j1]=d;
}
}
if (j>=m-2||j1>=m-2) ///由于从as数组0开始的所以m-2.
cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
return 0;
}
方法二:最长递增子序列
这个方法会用到二分查找stl中的函数,对于lower_bound和upper_bound的相关用法可以参考此链接lower_bound和upper_bound详解
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f
int dp[10010];//dp[i]表示长度为i+1的子序列末尾元素最小值;
int a[10010];
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
dp[i]=INF;//不可以用memset对数组赋值INF,只能赋值0或-1;
//可以用fill(dp,dp+n,INF);
}
for(int i=0;i<n;i++)
{
*lower_bound(dp,dp+n,a[i])=a[i];//找到>=a[i]的第一个元素,并用a[i]替换;
///upper_bound(dp,dp+dnum,b[i])-dp; //upper_bound数组中第一个不大于b[i]的 数,如果都小于则是所要长度后的一个。
}
printf("%d\n",lower_bound(dp,dp+n,INF)-dp);//找到第一个INF的地址减去首地址就是最大子序列的长度;
}
return 0;
}
方法三:模拟
1、首先,分析数据量,n最大有10^5,而且T也不小,明显求最长递增子序列或者递减子序列看其是否为n-1明显是不行的。
2、接下来,我们分析如何去掉一个数的问题,我们很容易想到,对于这个数,一定是改变递增或者递减的一个数,那么问题其实就转化到找折点上来。
3、那么我们来找递增和递减的折点,这里记住,不要分着去找,如果分着去找,那么时间复杂度就变成了2NT,我知道我代码写的挫,但是被卡了2NT实在是无力吐槽,改成NT才过的。那么我们还是稳妥点,尽量用更少的操作来完成找折点的问题。
4、这里注意,只有一个折点的时候才可能去掉某一个数之后使得序列是Sorted的,所以这里我们找到的递增折点或者递减折点的数量要进行一个控制,之后找到这个唯一的一个递增/递减折点之后,分情况进行讨论即可。
#include<stdio.h>
#include<string.h>
using namespace std;
int a[100050];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
memset(a,0,sizeof(a));
int n;
scanf("%d",&n);
int pos2;
int cont2=0;
int pos1;
int cont1=0;
scanf("%d",&a[0]);
for(int i=1;i<n;i++)
{
scanf("%d",&a[i]);
if(a[i]<a[i-1])
{
pos1=i;
cont1++;
}
if(a[i]>a[i-1])
{
pos2=i;
cont2++;
}
}
if(pos1==1&&cont1==1)
{
if(a[pos1]<=a[pos1+1])
{
printf("YES\n");
continue;
}
}
if(cont1==0)
{
printf("YES\n");
continue;
}
if(cont1==1&&pos1==n-1)
{
if(a[pos1])
printf("YES\n");
continue;
}
if(cont1==1)
{
if(a[pos1+1]>=a[pos1-1])
{
printf("YES\n");
continue;
}
if(pos1-2>=0&&a[pos1-2]<=a[pos1]&&a[pos1-2]<=a[pos1])
{
printf("YES\n");
continue;
}
}
//
if(pos2==1&&cont2==1)
{
if(a[pos2]>=a[pos2+1])
{
printf("YES\n");
continue;
}
}
if(cont2==0)
{
printf("YES\n");
continue;
}
if(cont2==1&&pos2==n-1)
{
printf("YES\n");
continue;
}
if(cont2==1)
{
if(a[pos2+1]<=a[pos2-1])
{
printf("YES\n");
continue;
}
if(pos2-2>=0&&a[pos2-2]>=a[pos2+1]&&a[pos2-2]>=a[pos2])
{
printf("YES\n");
continue;
}
}
printf("NO\n");
}
}