尺取法

1、算法概述

尺取法简单来说是一种利用双指针遍历获取满足条件的区间的算法。其为一种线性算法。记(l, r)为一个序列内以l为起点的最短合法区间,如果有rl的增大而增大,使用尺取法。具体做法就是不断枚举l,同时求出r,由于rl增大而增大,所以r只有 n 次变化机会,所以时间复杂度为O(n)

尺取法需要满足的条件: 区间权值大小满足随区间长度单调变化,即区间越长区间权值越小(或越大)。

尺取法的优点: 不会去枚举到一定不满足条件的区间。

2、算法模板

for(int l = 1; l <= n; ++l)
{
while(r < n && sum < s)
{
++r;
sum += arr[r];
}
if(sum >= s && r - l + 1 <= n)
{
ans = min(ans, r - 1 + 1);
}
sum -= arr[l];
}

说明: 首先枚举l,就是相当于给所有状态按左端点分类,去枚举以l为起点的第一个满足条件的区间。确定l后,++r就相当于剪枝一定不满足条件的区间,在此过程中维护区间和sum,当我们发现终于枚举到第一个满足条件的区间时,去统计一下区间长度最小值。sum -= arr[l]逻辑很关键,它利用了尺取法的单调性,sum减去后,下一轮枚举新的l,这样lr是以新的l为起点的最接近满足条件的区间,这样省得去枚举以新l为起点,右端点为r之前得一定不满足条件得区间。

3、算法示例

1、找到指定和的整数对

  • 问题描述

输入n(n <= 100000)个整数,放在数组arr中,找出其中的两个数,它们的和等于整数m(假设肯定有解)。

  • 输入

21 4 5 6 13 65 32 9 23
28

  • 输出

5 23

  • 说明

输入第一行是n个整数,第二行是m = 28,输出5 + 23 = 28

题解

该题有多种解法:

1)两重循环暴力枚举,复杂度为O(n^2),超时。

2)二分法,首先对数组从小到大排序,复杂度O(nlogn),然后从头到尾处理数组中的每个元素,在大于arr[i]的数中使用二分查找一个等于m - arr[i]的数,复杂度O(nlogn),总复杂度仍然为O(nlogn)

3)哈希表,分配一个哈希表,把n个数放进去,逐个检查arr中的n个数,检查m - arr[i]在哈希表中是否有值,复杂度O(n),空间换时间,复杂度最低。

4)尺取法,首先对数组从小到大排序,然后,设置两个变量ij,分别指向头和尾,i初值是 0,j初值是n - 1,然后让ij逐渐向中间移动,检查arr[i] + arr[j],如果大于m,就让j减 1,如果小于m,就让i加 1,直至arr[i] + arr[j] = m,排序复杂度O(nlogn),检查的复杂度O(n),合起来总复杂度O(nlogn)

void FindSum(int *arr, int n, int m)
{
if(arr == nullptr)
{
return;
}
sort(arr, arr + n - 1); // 排序
int i = 0;
int j = n - 1;
while(i < j)
{
int sum = arr[i] + arr[j];
if(sum > m)
{
--j;
}
if(sum < m)
{
++i;
}
if(sum == m)
{
std::cout << arr[i] << " " << arr[j] << "\n";
++i;
}
}
}

说明

在这个题目中,尺取法不仅效率高,而且不需要额外的空间。把题目的条件改变一下,可以变化为类似的问题,例如:判断一个数是否为两个数的平方和。其实也能用同向扫描来做。

2、子序列

  • 问题描述

有一个长度为 N 的正整数序列(10 < N < 100000),每个数字都小于等于 10000,再给定一个正整数 S(S < 100000000),试求一个连续子序列,使得该序列的数字之和大于或等于 S,并要求该子序列尽量短。

  • 输入

输入多组数据,每组数据第一行为两个正整数 N 和 S,中间用空格隔开。第二行给出这个序列,每两个整数之间用空格隔开。输入文件以 EOF 结尾。当这个连续子序列不存在时输出 0。

  • 输出

对于每组数据,输出一个整数,代表满足要求的最短子序列长度,每组数据输出占一行。

题解

const int N = 500007;
const int M = 510007;
const int INF = 0x3f3f3f3f;
int n = 0;
int S = 0;
int arr[N] = {0};
int Solve()
{
int res = INF;
int s = 1;
int t = 1;
int sum = 0;
while(1)
{
while(t <= n && sum < S)
{
sum += arr[t++];
}
if(sum < S)
{
break;
}
res = min(res, t - s);
sum -= arr[s++];
}
if(res == INF)
{
res = 0;
}
return res;
}
int main()
{
int t = 0;
std::cin >> t;
while(t--)
{
std::cin >> n >> S;
for(int i = 1; i <= n; ++i)
{
std::cin >> arr[i];
}
std::cout << Solve() << "\n";
}
return 0;
}
posted @   HOracle  阅读(482)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示