章节概述

双指针(又称尺取法)通过两个指针在数组上协同移动,将 O(n^2) 暴力优化为 O(n),
常用于有序数组的查找、区间统计等问题。

核心原理

1. 数学基础

双指针的核心是利用单调性避免重复遍历。每个指针最多移动 n 次,总复杂度 O(n)。

2. 三种模式

模式指针方向典型问题
对撞双指针 (左右)l→ ←r有序数组两数之和
同向双指针 (快慢)slow→ fast→去重、删除元素
滑动窗口 (同向)l→ r→最短/最长满足条件的区间

3. 对撞双指针

l = 0, r = n-1
while (l < r):
    sum = a[l] + a[r]
    if sum == target: 找到
    if sum < target: l++    // 和太小,增大
    else: r--               // 和太大,减小

4. 同向双指针 (快慢)

slow = 0
for fast in 1..n-1:
    if a[fast] != a[slow]:
        slow++
        a[slow] = a[fast]
return slow + 1   // 去重后长度

关键数据结构


同向双指针(快慢指针)

两个指针同向移动,常用于去重、删除元素、链表操作等。

// 示例:移除有序数组中重复元素,返回新长度
int removeDuplicates(int a[], int n) {
    int slow = 0;
    for (int fast = 1; fast < n; fast++)
        if (a[fast] != a[slow])
            a[++slow] = a[fast];
    return slow + 1;
}

对撞双指针

两个指针从两端向中间移动,常用于有序数组中查找两数之和。

// 示例:在有序数组中找两数之和等于 target
bool twoSum(int a[], int n, int target) {
    int l = 0, r = n - 1;
    while (l < r) {
        int sum = a[l] + a[r];
        if (sum == target) return true;
        if (sum < target) l++;
        else r--;
    }
    return false;
}

P1102 A-B 数对

题目: 给出 N 个正整数和一个数 C,求满足 A - B = C 的数对 (A,B) 个数。

思路: 排序后用对撞双指针或二分。转化为 B = A - C,对每个 A 统计 B 的出现次数。

#include <iostream>
#include <algorithm>
using namespace std;
int a[200005];
 
int main() {
    int n, c;
    long long ans = 0;
    cin >> n >> c;
    for (int i = 0; i < n; i++) cin >> a[i];
    sort(a, a + n);
    for (int i = 0; i < n; i++) {
        int target = a[i] - c;
        ans += upper_bound(a, a + n, target) - lower_bound(a, a + n, target);
    }
    cout << ans << endl;
    return 0;
}

P1147 连续自然数和

题目: 把一个自然数 M 表示为连续的若干自然数之和,输出所有方案。

思路: 右指针 r 向右移动,维护区间 [l, r] 的和。若和大于 M 则移动左指针 l;等于 M 则记录答案。

#include <iostream>
using namespace std;
 
int main() {
    int M;
    cin >> M;
    int l = 1, r = 1, sum = 1;
    while (l <= r && r <= M / 2 + 1) {
        if (sum == M) {
            cout << l << " " << r << endl;
            sum -= l++;
        } else if (sum < M) {
            sum += ++r;
        } else {
            sum -= l++;
        }
    }
    return 0;
}

P1638 逛画展

题目: 博览会有 n 幅画排成一行,共有 m 位画家。求一个最短的连续区间,使得该区间内包含所有 m 位画家的作品。

思路: 滑动窗口(同向双指针)。右指针扩展,统计区间内画家数量;当包含全部画家时,收缩左指针。

#include <iostream>
using namespace std;
int a[1000005], cnt[2005];
 
int main() {
    int n, m, kinds = 0;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    int l = 1, minLen = n + 1, ansL = 1, ansR = n;
    for (int r = 1; r <= n; r++) {
        if (cnt[a[r]] == 0) kinds++;
        cnt[a[r]]++;
        while (kinds == m && l <= r) {
            int len = r - l + 1;
            if (len < minLen) {
                minLen = len;
                ansL = l;
                ansR = r;
            }
            cnt[a[l]]--;
            if (cnt[a[l]] == 0) kinds--;
            l++;
        }
    }
    cout << ansL << " " << ansR << endl;
    return 0;
}

推荐练习题(洛谷)


相关技巧


多平台练习

| 洛谷 | 本题单 | 竞赛基础 |
| POJ (北大) | PKU JudgeOnline | 经典题目,适合巩固 |
| HDU (杭电) | HDU OJ | 暑期多校训练 |
| Codeforces | Codeforces | 国际竞赛,适合提升 |