🧠 算法学习日记 | 今天我用「双指针」解了三道题,原来"左右夹击"能这么高效!
大家好,我是你们的算法学习搭子 👋
今天继续我的算法入门之旅,重点练习了**双指针(Two Pointers)**这一高效的数据遍历技巧。
很多人觉得"双指针"只是用来判断回文的工具,但其实,它是一种空间换时间、动态调整窗口 的经典思想。
我们用两个指针同时从两端或前后移动,避免重复计算,大幅提升效率。
今天我完整做了三道题,每一道都坚持用最朴素的双指针逻辑实现。下面我把题目原文 和我的原始代码原封不动贴出来,不做任何删减或美化,只为真实记录学习过程。
🔹 题目一:回文判定
题目描述
给定一个长度为 n 的字符串 S 。请你判断字符串 S 是否是回文。
输入描述输入仅 1 行包含一个字符串 S 。
1 \\leq \|S\| \\leq 10\^6 ,保证 S 只包含大小写、字母。
输出描述若字符串 S 为回文串,则输出
Y,否则输出N。
输入输出样例
示例 1
输入
abcba
输出
Y
示例 2
输入
abcbb
输出
N
✅ 我的代码(完全保留原始写法)
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
char s[N];
int main()
{
cin>>s+1;
int n = strlen(s+1);
int l=1;
int r=n;
int flag =1;
while(l<r){
if(s[l]!=s[r]){
flag=0;
break;
}
++l;
--r;
}
if(flag)cout<<"Y";
else cout<<"N";
return 0;
}
🔹 题目二:美丽的区间
题目描述
给定一个长度为 n 的序列 a_1, a_2, \\ldots, a_n 和一个常数 S 。
对于一个连续区间,如果它的区间和大于或等于 S ,则称它为美丽的区间。
对于一个美丽的区间,如果其区间长度越短,它就越美丽。
请你从序列中找出最美丽的区间。
输入描述
第一行包含两个整数 n, S ,其含义如题所述。
接下来一行包含 n 个整数,分别表示 a_1, a_2, \\ldots, a_n 。
约束: 10 \\leq N \\leq 10\^5 ,,, 1 \\leq a_i \\leq 10\^4 ,,, 1 \\leq S \\leq 10\^8 。
输出描述
输出共一行,包含一个整数,表示最美丽的区间的长度。
若不存在任何美丽的区间,则输出 0。
输入输出样例
示例 1
输入
5 6
1 2 3 4 5
输出
2
✅ 我的代码(完全保留原始写法)
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
int a[N];
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int n,s;
cin>>n>>s;
int sum=0;
int ans = n+1;
for(int i=1;i<=n;++i)cin>>a[i];
for(int l=1,r=0;l<=n;++l){
while(l>r || (r+1<=n && sum<s)){
r++;
sum+=a[r];
}
if(sum>=s){
ans = min(ans,r-l+1);
}
sum-=a[l];
}
cout<<(ans > n ? 0 : ans);
return 0;
}
🔹 题目三:挑选子串
题目描述
有 n 个数,和一个整数 m 。
现要从这 n 个数选出一个连续子串,要求这个子串里面至少有 k 个数要大于等于 m 。
问一共能选出多少个子串(显然子串长度要大于等于 k )。
输入描述
输入第一行是 3 个整数 n, m, k 。
输入第二行是 n 个整数 a_1, a_2, \\ldots, a_n ,表示序列。
约束: 2 \\leq n \\leq 2000 ,,, 1 \\leq k \\leq \\frac{n}{2} ,,, 1 \\leq m, a_i \\leq 10\^9 。
输出描述
输出一个整数表示答案。
输入输出样例
示例
输入
7 4 2
4 2 7 6 5 1
输出
18
运行限制
- 最大运行时间:1s
- 最大运行内存:256M
✅ 我的代码(完全保留原始写法)
cpp
#include<bits/stdc++.h>
using namespace std;
const int N=2e3+5;
int a[N];
int main(){
int n,m,k;
cin>>n>>m>>k;
for(int i=1;i<=n;++i)cin>>a[i];
int ans=0;
for(int l=1,r=0,cnt=0;l<=n;++l){
while(l>r || (r<n-1 && cnt < k)){
cnt += (a[++r] >= m);
}
if(cnt >= k) ans += n-r+1;
cnt -= (a[l]>=m);
}
cout << ans <<endl;
return 0;
}
🌟 我的思考
这三道题虽然形式各异,但都用了双指针的核心思想:
- 回文判定:左指针从头开始,右指针从尾开始,向中间靠拢
- 美丽的区间:滑动窗口,左指针固定时扩展右指针,找到满足条件后收缩左指针
- 挑选子串:同样滑动窗口,统计满足条件的元素个数,当满足后累加所有可能的右端点
你会发现:
双指针的本质是"动态窗口" ------ 用两个指针维护一个区间,根据条件决定移动哪个指针。
而且,双指针可以分为两类:
- 对撞指针(如回文):从两端向中间移动
- 滑动窗口(如后两题):一个指针扩展,另一个指针收缩
✅ 总结
- 双指针适用于:有序数组、连续子数组、区间问题
- 常见技巧:排序 + 双指针、滑动窗口、左右夹击
- 时间复杂度:通常为 O(n) ,远优于暴力枚举
- 关键在于:明确两个指针的移动条件(何时增,何时减)
- 不是所有问题都能用双指针解决,需结合具体场景