算法知识图谱总览 / 离线与杂项技巧族 / 双指针

双指针

P0basicdeepG-E-7

What · 是什么

同向/相向移动两指针维护区间信息:O(n)解决单调性区间问题,莫队基础

双指针(Two Pointers)是用两个指针在序列上同向或相向移动来维护区间信息的技巧。同向双指针(快慢指针)维护滑动窗口;相向双指针(左右指针)利用有序性从两端逼近。可在 O(n) 时间内解决具有单调性的区间问题,是莫队算法的基础。

Why · 为什么

直觉 · Intuition

暴力枚举区间是 O(n^2)。如果区间具有单调性(如正数之和随右端点递增),则可以只移动一个指针:当区间不满足条件时移动左指针缩小,满足时移动右指针扩大。两个指针各自只前进不后退,总共最多各走 n 步 → O(n)。

原理 · Principle

同向双指针(滑动窗口):l=0, 遍历 r 从 0 到 n-1,当 [l,r] 不满足条件时 l++。利用单调性:若 [l,r] 满足则 [l+1,r] 也满足(缩小只会更好),若不满足则需 l++ 或 r++。相向双指针:l=0, r=n-1,根据 a[l]+a[r] 与 target 的关系移动 l++ 或 r--,利用有序性从两端逼近答案。

历史 · History

双指针是竞赛和面试中最基础的技巧之一。CPH(Competitive Programmer's Handbook)ch5 将其归为 Complete Search 的高效变体。经典应用包括 LeetCode 167(两数之和II)、713(乘积小于K的子数组)。莫队算法即基于双指针的区间移动。

How · 实现

伪代码

# 同向双指针(滑动窗口): 求满足条件的最长区间
l = 0
for r in range(n):
  add(arr[r])  # 扩展右端
  while not valid():  # 不满足条件时收缩
    remove(arr[l])
    l += 1
  ans = max(ans, r - l + 1)

# 相向双指针: 有序数组两数之和
l, r = 0, n-1
while l < r:
  s = arr[l] + arr[r]
  if s == target: found
  elif s < target: l += 1
  else: r -= 1

Python 实现

def subarray_product_less_than_k(nums, k):
    """LeetCode 713: 乘积小于K的子数组个数。
    同向双指针(滑动窗口), 利用正整数乘积单调性。
    """
    if k <= 1:
        return 0
    prod = 1
    l = 0
    ans = 0
    for r in range(len(nums)):
        prod *= nums[r]
        while prod >= k:
            prod //= nums[l]
            l += 1
        ans += r - l + 1  # 以r结尾的合法子数组个数
    return ans


def two_sum_sorted(numbers, target):
    """LeetCode 167: 有序数组两数之和II。
    相向双指针, 利用有序性从两端逼近。
    返回两数下标(1-indexed)。
    """
    l, r = 0, len(numbers) - 1
    while l < r:
        s = numbers[l] + numbers[r]
        if s == target:
            return [l + 1, r + 1]  # 1-indexed
        elif s < target:
            l += 1
        else:
            r -= 1
    return []


def min_subarray_len(target, nums):
    """LeetCode 209: 长度最小的子数组(和>=target)。
    同向双指针。
    """
    l = 0
    s = 0
    ans = len(nums) + 1
    for r in range(len(nums)):
        s += nums[r]
        while s >= target:
            ans = min(ans, r - l + 1)
            s -= nums[l]
            l += 1
    return ans if ans <= len(nums) else 0


def is_subsequence(s, t):
    """LeetCode 392: 判断s是否为t的子序列。
    双指针匹配。
    """
    i = 0
    for ch in t:
        if i < len(s) and s[i] == ch:
            i += 1
    return i == len(s)


if __name__ == '__main__':
    # 测试乘积小于K的子数组
    assert subarray_product_less_than_k([10, 5, 2, 6], 100) == 8
    print(f'Subarray product < 100: {subarray_product_less_than_k([10, 5, 2, 6], 100)}')  # 8

    # 测试两数之和
    assert two_sum_sorted([2, 7, 11, 15], 9) == [1, 2]
    print(f'Two sum [2,7,11,15] target=9: {two_sum_sorted([2, 7, 11, 15], 9)}')  # [1,2]

    # 测试最小子数组
    assert min_subarray_len(7, [2, 3, 1, 2, 4, 3]) == 2
    print(f'Min subarray len target=7: {min_subarray_len(7, [2, 3, 1, 2, 4, 3])}')  # 2

    # 测试子序列
    assert is_subsequence('abc', 'ahbgdc') == True
    assert is_subsequence('axc', 'ahbgdc') == False
    print(f'Is subsequence abc/ahbgdc: {is_subsequence("abc", "ahbgdc")}')  # True

    print('All two-pointer tests passed!')
PyPy 提示PyPy 对双指针的简单数组操作优化极佳,性能接近 C;双指针是最轻量的算法之一,几乎无性能瓶颈。
时间复杂度O(N) — 两个指针各最多移动 N 次
空间复杂度O(1) — 仅需常数额外空间(同向) / O(1) (相向)
注意 · 局限需要区间具有单调性(否则无法只前进不后退);不适用于需要随机访问区间信息的问题(应用前缀和/线段树);相向双指针要求序列有序。

易错点

例题

OJ题号难度考点链接
LeetCode7132乘积小于K的子数组: 同向双指针经典模板https://leetcode.cn/problems/subarray-product-less-than-k/
LeetCode1672两数之和II: 相向双指针, 利用有序性https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/
LeetCode2092最小长度子数组: 滑动窗口双指针https://leetcode.cn/problems/minimum-size-subarray-sum/
步骤迷你图(节点 = 算法步骤 · 边 = 流程 · 绿色 = 起始步骤)
下游 · 依赖本知识点

信源

✓ = 已核验 · △ = 未核验