尺取法:简单来说,我可以把两重循环转化为一重循环,从而把时间复杂度从 O(n^2) 提高到 O(n)。
for(int i = 0; i < n; i++) //i从头扫到尾
for(int j = n-1; j >= 0; j--){ //j从尾扫到头
......
}
其中 i 从 0 循环到 n-1,j 反过来从 n−1 循环到 0 。这两重循环的时间复杂度是 O(n^2) 的。
如何把两重循环变为一个循环?
我可以在一个循环中一起处理 i,j,代码如下,这样复杂度就从 O(n^2)变成了 O(n)。
for (int i = 0, j = n - 1; i < j; i++, j--) {
......
}
诶等等,这?你在耍我呢?这不是瞎扯淡嘛?这两重循环凭啥变成下面的一重循环了?
em,我这不是还没说完嘛。。。我的局限性很大,因为我有 i < j 的一个小限制,也就是说 i从头到尾走、j从尾到头走,两者可以中间位置相会。所以我的应用场合并不多。
好吧,我还以为你是万能的,原来只能应用在有限场合啊…诶你这个有限场合大概是什么样的场合啊?还有你这个一重循环的代码。。。就不能写的好看点?
有限场合一般指代和区间有关的问题;如果你觉得 for 循环不好看,那就来看看我的 while 写法吧。
//用while实现:
int i = 0, j = n - 1;
while (i < j) { //i和j在中间相遇。这样做还能防止i、j越界
...... //满足题意的操作
i++; //i从头扫到尾
j--; //j从尾扫到头
}
嗯,这代码看起来还是很舒服的。
以上是被称为「反向扫描」的尺取法,另外还有一种「同向扫描」。
你说的这「同向扫描」又是咋样的?
我对尺取法的概念做了总结,你看看下面就会明白了:
把循环指针 i 、j称为「扫描指针」,在尺取法中,这两个指针 i、j,有两种扫描方向:
反向扫描。i、j方向相反,i 从头到尾,j从尾到头,在中间相会。也可以把反向扫描的 i、j指针称为「左右指针」。
同向扫描。i、j方向相同,都从头到尾,但是速度不一样,比如可以让 j跑在 i前面。也可以把同向扫描的 i、j 指针称为「快慢指针」,此时由于 i 和 j 速度不同,i 和 j 之间在序列上产生了一个大小可变的「滑动窗口」,这是尺取法的优势,有灵活的应用。
注意,用尺取法的最关键之处在于,两个指针 i、j 在总体上只能有一个循环,例如:i 循环一遍,对应的 j 只能跟随 i 循环一遍。这样才能实现计算复杂度从 O(n^2)到 O(n)的优化。
算法练习题:回文判定
题目描述
给定一个长度为 nn 的字符串 S。请你判断字符串 S 是否回文。
输入描述
输入仅 11 行包含一个字符串 S。
1≤∣S∣≤10^6
,保证 S只包含大小写、字母。
输出描述
若字符串 S为回文串,则输出 Y,否则输出 N。
样例输入
abcba
样例输出
Y
这题是「反向扫描」最直接的应用,似乎不需要任何解释吧
我的代码如下:
#include <bits/stdc++.h>
using namespace std;
int main(){
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 << "Y" << endl;
else cout << "N" << endl;
return 0;
}