尺取法(又称滑动窗口法、双指针法)是一种在数组或字符串中高效查找满足特定条件的子数组或子字符串的算法。它的基本思想是通过两个指针(通常称为左指针和右指针)来维护一个窗口,通过移动这两个指针来调整窗口的大小和位置,从而找到满足条件的子数组或子字符串。
尺取法一般用法:
当用i和j分别扫描区间时,需要双重循环,时间复杂度为O(n2),代码如下:
for(int i=0;i<n;i++)
for(int j=n-1;j>=0;j--)
......
用尺取法优化:
int i=0,j=n-1;
while(i<j)
{
i++;
j--;
}
在尺取法中,i和j有两种扫描方向。
1.反向扫描,即i和j方向相反,例子如上。
2.同向扫描,i和j方向相同,这时把i和j指针称之为”快慢指针“,由于快慢指针所产生的距离时可变的,所以也称之为“滑动窗口”。
反向扫描:
例题:
问题描述
输入n ( n≤100,000)个整数,放在数组a[]中。找出其中的两个数,它们之和等于整数m(假定肯定有解)。题中所有整数都是int型。
样例输入:
21 4 5 6 13 65 32 9 23
28
样例输出:
5 23
说明:样例输入的第一行是数组a[],第2行是m = 28。样例输出5和23,相加得28。
void find_sum(int a[], int n, int m){
sort(a, a + n); //先排序,复杂度O(nlogn)
int i = 0, j = n - 1; //i指向头,j指向尾
while (i < j){ //复杂度O(n)
int sum = a[i] + a[j];
if (sum > m) j--;
if (sum < m) i++;
if (sum == m){
cout << a[i] << " " << a[j] << endl; //打印一种情况
i++; //可能有多个答案,继续
}
}
}
“回文串”是一个正读和反读都一样的字符串,比如“level”或者“noon”就是回文串。写一个程序判断读入的字符串是否是“回文”。如果是,输出“yes”,否则输出“no”。
#include <bits/stdc++.h>
using namespace std;
int main(){
int n;
cin >> n; //n是测试用例个数
while(n--){
string s; cin >> s; //读一个字符串
bool ans = true;
int i = 0, j = s.size() - 1; //双指针
while(i < j){
if(s[i] != s[j]){
ans = false;
break;
}
i++; j--; //移动双指针
}
if(ans) cout << "yes" << endl;
else cout << "no" << endl;
}
return 0;
}
同向扫描:
例题:
给定一个长度为n的数组a[]和一个数s,在这个数组中找一个区间,使得这个区间之和等于s。输出区间的起点和终点位置。
样例输入:
15
6 1 2 3 4 6 4 2 8 9 10 11 12 13 14
6
样例输出:
0 0
1 3
5 5
6 7
void findsum(int *a, int n, int s){
int i = 0, j = 0;
int sum = a[0];
while(j < n){ //下面代码中保证 i<=j
if(sum >= s){
if(sum == s) printf("%d %d\n", i, j);
sum -= a[i];
i++;
if(i>j) {sum = a[i]; j++;} //防止i超过j
}
if(sum < s){
j++;
sum += a[j];
}
}
}
对于这道题,通过滑动窗口,控制窗口大小来解决,首先让ij指针指向起点,由于i j指针速度不同,但它们又同时满足小于数组长度,所以让速度快的指针小于数组长度,让这个来作为while停止条件。设sum为初始值a[0],随后对sum进行讨论,它有三种情况:大于s,等于s,小于s,在操作时,要考虑到 不能让i超过j,如果i超过j了,就让i指针不动,j向前追赶,当sum小于s时,说明i这时也小于j,i和j指针之间所指向的数的和为sum,为了让sum等于s,所以就让窗口变大,即j增加,j增加也就意味着sum值增加,从而继续while循环。