尺取法
(ps:参考书籍:挑战程序设计)
尺取法:尺取法通常是对数组保存一对下标,即所选取的区间的左右端点,然后根据实际情况不断地推进区间左右端点以得出答案。
是我们经常需要用到技巧。 (尺取法还是比较好懂的)
什么情况下能使用尺取法?
如果题目要求,给出一个数列,求其中一段连续的子序列的总和要大于/或小于等一个数值,然后要求出最小的长度。
换句话说,必须得是连续的一段子序列:就像它的名字,尺取,一定是要连续一段。然后从前往后行走。
还有就得看具体题目了。
相关运用
直接参考书中例题。
如图,尺取步骤:
1。开始第一步,我们直接从左到右 先累加到 大于等于 S,记录此时长度
2。然后减掉最左边的一个数值,如果还大于等于S,更新长度,重复2
否则往右再加,一直加到 又是大于等于S,重复2
3。走到数列结束,此时长度也更新到最小值,结束啦。。。。。。。
因为是从左走到右,所以时间复杂度为 O(n)
代码
代码,也可看做尺取法的模板
#include<iostream>
using namespace std;
const int maxn = 10005;
int n;
int S;
int a[maxn];
int main(){
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i];
}
int res =n+1; //长度
int s=0,t=0,sum=0; //s:当前最左边位置 t:当前最右边位置
// sum:是否大于等于S
for(;;){
while(t<n && sum<S){
sum += a[t++];
}
if(sum<S) break; //即是累加,也是最后出口
res = min(res,t-s);
sum -= a[s++];
}
if(res >n){ //不存在这样的子序列
res =0; //输出0
}
cout<<res<<endl;
return 0;
}
再看这道,基本思路一样,
#include<iostream>
#include<set>
#include<map>
using namespace std;
const int maxn = 1e6+5;
int p;
int a[maxn];
int main(){
//读入
cin>>p;
for(int i=0;i<p;i++){
cin>>a[i];
}
//用set,可自动去重,排序
set<int> all;
for(int i=0;i<p;i++){
all.insert(a[i]);
}
int n=all.size();
//尺取法
int s=0,t=0,num=0;
map<int,int> count;
int res=p;
for(;;){
while(t<p && num<n){
if(count[a[t++]] ++== 0){ //如果count[a[t]]==0;那就加1;然后t++;
//一直加到num== 页数n
num++;
}
}
if(num<n) break; //一直累加到num==n,或是到最后 循环出口
res = min(res,t-s);
if(-- count[a[s++]] == 0){ //把目前最前面的一个去掉
num--;
}
}
cout<<res<<endl;
return 0;
}