什么是尺取法?对一个序列进行扫描其区间。
模板:这里是写成伪代码的形式
int a[n];
sort(a , a + n);//看情况判断是否需要排序
//while循环
int i = 0;
int j = n - 1;
wile(i < j)
{
...;//用途
i++;//头
j--;//尾
}
//for循环
for(int i = 0,j = n - 1;i < j;i++ , j--)
{
...;//用途
}
尺取法有两种扫描途径:
1.反向扫描:
找一个n数列中两个元素的和 = c
输入:n c
a[n] = {...}
输出:两个数分别是多少
(1)暴力 O(n^2) ,time limit exceed
(2)二分查找:先排序,随后遍历整个数组,锁定一个a[i],然后二分查找m - a[i]
(3)尺取法:跟二分法的时间复杂度是一样的,但是代码比较简洁且容易思考
假设n = 9
a[n] = {21,4,5,6,13,65,32,9,23}
为了便于扫描加和,这里咱们不妨将其排序,利用C++的sort函数
随后按照模板设置两个指针,分别指向头和尾不断地向中间移动,如果sum > c就使j--,来保证加和的数变小,这也是为什么咱们要排序,如果sum < c就使i++.当 a[i] + a[j] = c就 cout 输出并让 i++ 来继续查找,要是没有写 i++ 就会重复输出以至于死循环一直输入当前查找的数。
#include <bits/stdc++.h>
using namespace std;
const int N = 1000;
int a[N];
//查找数组内两个数的和=c的数
int main()
{
int n , c;
cin >> n >> c;
for(int i = 0;i < n;i++) cin >> a[i];
sort(a , a + n);
int i = 0,j = n - 1;
while(i < j)
{
int sum = a[i] + a[j];
if(sum > c) j--;
if(sum < c) i++;
if(sum == c)
{
cout << i << ' ' << j << endl;
i++;
}
}
}
判断回文串:
输入:2level
abcda
输出:YES
NO
这道题就很经典了,这道题咱们在前面讲述动态规划讲到很多子序列问题 ,这里咱们利用尺取法解题,定义i,j两个指针分别指向头和尾不断地缩小区间,如果指针指向的两个地址的元素不相等,那么就直接返回false。
这里咱们是利用了C++的字符串类型,便于指针的遍历。
#include <bits/stdc++.h>
using namespace std;
const int N = 1000;
int main()
{
int n;
cin >> n;
while(n--)
{
string a;
cin >> a;
bool ans = true;
int i = 0,j = a.size() - 1;
while(i < j)
{
if(a[i] != a[j])
{
ans = false;
break;
}
i++;
j--;
}
if(ans)
cout << "YES" << endl;
else
cout << "NO" << endl;
}
return 0;
}
2.同向扫描:
同向扫描实际就是类似滑动窗口一样来扫描整个数组每个区间段
寻找区间和:
给定一个数组,请找出这个数组一段区间和=c的区间
输入:15(n = 15)
6 1 2 3 4 6 4 2 8 9 10 11 12 13 14
6(c = 6)
输出:0 0
1 3
5 5
6 7
(1)因为要区间和并且这里要输出其区间,所以显而易见这里并不需要排序。
(2)另外这里要取其区间和,咱们可以从a[i]+...+a[j],但是其运算量可想而知,所以对于区间和,咱们应该联想到的是前缀和的知识点(如果不了解前缀和可以看我的算法专栏,里面有解释前缀和与差分的使用,这里不过多的解释了),所以我们可以在指针移动的同时加减其前位置,以此达到计算区间和。
(3)然后对于指针的运动,这里取同向指针是因为便于做前缀和的计算,咱们也可以去反向扫描,但是咱们就需要求出前缀和数组来进行计算。如果sum = c就输出其区间,如果sum > c那么就使 i++ 并让 sum -= a[i]. 如果 sum < c 我们可以让 j++ 并让 sum += a[j] ,注意这里 i 与 j 在让 sum加和的值,这里咱们看写出代码的先后顺序加以分析:
(4)同向扫描要注意不可以让 i 的位置超过 j ,保证i <= j 。
#include iostream>
using namespace std;
const int N = 1000;
int a[N];
int main()//伸缩区间
{
int n,c;
cin >> n >> c;
for(int i = 0;i < n;i++)
cin >> a[i];
int i = 0,j = 0;
int sum = a[0];
while(i < j)
{
if(sum >= c)
{
if(sum == c)
cout << i << ' ' << j << endl;
sum -= a[i];//前缀和思想
i++; //注意顺序
if(i > j)//同向扫描要注意i不可以超过j
{
j++;//注意顺序
sum += a[j];
}
}
else{
j++;
sum += a[j];
}
}
}
这里讲述的是两个指针
特殊情况:
如果在数组中存在两个相同的数
作差:
A - B = C
输入: 6 3
8 4 5 7 7 4
首先这道题同样是遍历数组,首先将其排序,然后这里要注意排序后结果为4 4 5 7 7 8
如果按照两个指针同向扫描,那么4 4 7 7 这里会出现四种情况,且随着指针移动会错过两种情况,那么这个时候就需要定义一个指针k记录[j , k)区间内为相同元素。
#include <bits/stdc++.h>
using namespace std;
int a[N];
int main()
{
int n,c;
cin >> n >> c;
int res = 0;
for(int i = 1;i <= n;i++) cin >> a[i];
sort(a + 1, a + n + 1);
for(int i = 1,j = 1,k = 1;i <= n;i++)
{
while(j <= n && a[j] - a[i] < c)
j++;
while(k <= n && a[k] - a[i] <= c)//这里的循环是记录k - j个元素相同
k++;
if(a[j] - a[i] == c && a[k - 1] - a[i] == c && k > 1)
res += k - j;//加上k - j个情况
}
cout << res;
return 0;
}
好了,今天的分享就到这里了,感谢收看,记得三连支持,后序继续更新算法知识点。