7.双指针算法
1. 核心定义与核心思想
- 定义 :通过两个指针在序列(或两个序列)上移动,协同完成任务的算法,本质是利用问题的单调性优化枚举效率。
- 核心价值:将朴素枚举的 O(n2) 时间复杂度优化为 O(n)(两个指针总移动次数不超过 2n)。
- 单调性本质:随着一个指针(如右指针)的向后移动,另一个指针(如左指针)只会向后移动或保持不动,不会向前回溯(可通过反证法证明:若左指针回溯,则与 "当前区间已满足条件" 矛盾)。
2. 算法分类
| 分类 | 适用场景 | 典型案例 |
|---|---|---|
| 指向两个序列 | 两个有序序列的合并 / 匹配 | 归并排序的合并步骤、两数之和(有序数组) |
| 指向一个序列 | 维护区间的特定性质(无重复、和满足条件等) | 快排的划分过程、最长无重复子序列、字符串拆分单词 |
3. 通用模板
C++
// 一维序列通用模板(i为右指针,j为左指针)
for (int i = 0; i < n; i++)
{
// 移动j,维护[j, i]区间满足目标性质
while (j < i && check(j, i)) j++;
// 具体业务逻辑(如计算区间长度、输出结果等)
// ...
}
核心思想:
for(int i = 0; i < n; i++)
{
for(int j = 0; j < n; j++)
{
O(n²)
}
}
- 关键:
check(j, i)是区间性质的判断函数,需根据题目自定义。
4. 经典例题解析
例题 1:字符串拆分单词(基础应用)
- 题目:输入一个字符串(单词间仅一个空格,无首尾空格),按行输出每个单词。
- 思路:
- 左指针
i指向单词起始位置,右指针j从i出发,扫描到空格停止(找到单词末尾); - 输出
[i, j-1]区间的字符,更新i = j + 1跳过空格。
- 左指针
- 代码实现:
C++
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str;
getline(cin, str);
int n = str.size();
int i = 0;
while (i < n)
{
int j = i;
// 移动j到单词末尾(空格前)
while (j < n && str[j] != ' ') j++;
// 输出当前单词
for (int k = i; k < j; k++) cout << str[k];
cout << endl;
i = j + 1; // 跳过空格,指向next单词起始
}
return 0;
}
例题 2:最长无重复字符的连续子序列(核心应用)
朴素做法
C++
for(int i = 0; i < n; i++)
{
for(int j = 0; j < n; j++)
{
if(check(j,i)) res = max(res, i-j+1);
}
}
双指针做法
C++
for(int i = 0; i < n; i++)
{
while(j <= i && check(j,i)) j++;
res = max(res, i-j+1);
}
- 题目 :给定长度为
n的整数序列,找出最长的不包含重复数字的连续子序列,输出其长度。 - 样例 :输入
[1,2,2,3,5],输出3(对应子序列[2,3,5])。 - 思路:
- 用
s[]数组记录当前区间[j, i]中每个数字的出现次数; - 右指针
i遍历序列,将a[i]加入区间(s[a[i]]++); - 若
s[a[i]] > 1(出现重复),移动左指针j并减少对应数字的计数(s[a[j]]--),直到区间无重复; - 每次更新区间长度的最大值。
- 用
- 代码实现:
C++
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N],s[N];
int main()
{
int n;
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
int res = 0;
for (int i = 0, j = 0; i < n; i++)
{
s[a[i]]++; // 加入当前数字
// 区间有重复,移动j
while (s[a[i]] > 1)
{
//j是起点,s[a[i]]>1,说明j到i-1已经遍历过了「a [i] 加入窗口后重复」,所以接下来是窗口调整是「j 右移,i 不变」
//「调整的终止条件」s[a[i]] > 1不成立
s[a[j]]--;
j++;
}
// 更新最长长度
res = max(res, i - j + 1);
}
cout << res << endl;
return 0;
}
- 扩展 :若数字范围极大(如 109),可改用哈希表(
unordered_map<int, int>)替代数组计数。
5. 补充扩展
- 双指针的本质是 "减少无效枚举",核心是找到区间的单调性;
- 常见变形:滑动窗口(固定窗口大小 / 动态窗口)、快慢指针(找链表环)等;
- 课后练习:LeetCode 3. 无重复字符的最长子串、LeetCode 209. 长度最小的子数组。