题目大概的意思是:给你一个整数数列,从中找出最小连续子序列的和不小于整数S。
数列最小为10,最大为100000,用暴力法估计会超时,我没有试过。
这里,我将用两种方法来求解这一题,一个时间复杂度为nlogn,另一个时间复杂度为n,不过两个提交了,时间都是79MS。没多大的区别。
1.时间复杂度为nlogn的方法:
主要的思路是用一个数组 sum 算出数列的前 i+1 个数的和,(i从0到n),然后每一个数列sum的每一个都加上S,再在数组sum中查找不小于sum[i] + S的位置ans。ans - i 就是不小于S的序列的长度,不断更新ans - i,就可以将解求出。
algorithm 中有一个函数lower_bound 就是求解一段数组中不小于一个数的下标。
下面的是AC的代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int a[100005], sum[100005];
int min(int x, int y)
{
return x > y ? y : x;
}
void solve(int n, int s)
{
int i, j;
for(i = 0; i < n; i++) //计算前i+1个数的和
sum[i + 1] = sum[i] + a[i];
if(sum[n] < s) //全部数之和小于s,直接输出0
printf("0\n");
else
{
int res = n;
for(j = 0; sum[j] + s <= sum[n]; j++) //在sum数组中,查找不小于s + sum【j】的位置,不断的更新
{
int ans = lower_bound(sum + j, sum + n, sum[j] + s) - sum; //返回不小于s + sum【j】的位置
res = min(res, ans - j); //比较res与ans - j谁小。不断更新
}
printf("%d\n", res);
}
}
int main()
{
int t, n, s;
scanf("%d", &t);
while(t--)
{
scanf("%d%d", &n, &s);
memset(sum, 0, sizeof(sum)); //初始化,
for(int i = 0; i < n; i++)
scanf("%d", &a[i]);
solve(n, s);
}
return 0;
}
2.时间复杂度为n的方法:
主要的思路就是:设置两个位置的标志,一个s为不小于m的序列的起始位置,一个t为不小于m的序列的末尾位置。一开始s = t = 0,设置一个序列和sum = 0;开始 sum += a【t++】直到 sum > m,算出序列长度 t - s;然后sum - a【s++】;看sum 是否还大于m,不大于,就继续加上a【t++】,否则则更新序列长度,sum - a【s++】;
不断的这样更新,就可以求出最小的序列长度。
对于文字不是很理解,可以看下面的图示。
下面的是AC的另一段代码:
#include <iostream>
#include <cstdio>
using namespace std;
int a[100005];
int min(int x, int y)
{
return x > y ? y : x;
}
void solve(int n, int m)
{
int res = n + 1;
int s, t, sum;
s = t = sum = 0;
while(1)
{
while(t < n && sum < m) //累加a数组,直到sum > m
{
sum += a[t++];
}
if(sum < m) //如果总和小于m,则退出
break;
res = min(res, t - s); //算出序列长度
sum -= a[s++]; //序列起始位置去掉一个,再进行循环
}
if(res > n)
res = 0;
printf("%d\n", res);
}
int main()
{
int t, n, s;
scanf("%d", &t);
while(t--)
{
scanf("%d%d", &n, &s);
for(int i = 0; i < n; i++)
scanf("%d", &a[i]);
solve(n, s);
}
return 0;
}