双指针算法 (Two Pointers)

0 阅读4分钟

1. 算法简介

双指针(Two Pointers)是一种非常优雅、直观且在面试中极其高频的算法思想。它并不是一种具体的数据结构,而是一种优化遍历策略的技巧。

核心思想:使用两个变量(通常叫 leftright,或者 fastslow)作为索引,在数组或链表上同时进行遍历。通过两个指针的相互配合,通常可以将原本 O(N2)O(N^2) 的时间复杂度降为 O(N)O(N),且空间复杂度保持在 O(1)O(1)

双指针主要分为两大流派:相向双指针同向双指针(快慢指针)


2. 相向双指针 (一左一右,向中间靠拢)

2.1 算法介绍

两个指针分别放置在数组(或序列)的两端。left 指针在最左侧起步,right 指针在最右侧起步。根据特定的条件,两个指针逐步向中间逼近,直到相遇或错开。

2.2 适用场景

条件反射:看到**“已排序”“升序”且需要查找组合**(如两数之和),或者需要进行首尾对称操作(如反转字符串、判断回文串)时,优先考虑相向双指针。

2.3 经典示例 1:有序数组的两数之和

题目:在已升序排列的整数数组中找出两个数,使它们的和等于目标值 target

Python 实现

def twoSum(numbers: list[int], target: int) -> list[int]:
    left, right = 0, len(numbers) - 1
    
    while left < right:
        current_sum = numbers[left] + numbers[right]
        if current_sum == target:
            return [left, right]
        elif current_sum > target:
            right -= 1 # 和太大,右指针左移找更小的
        else:
            left += 1  # 和太小,左指针右移找更大的
            
    return [-1, -1]

Java 实现

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int left = 0, right = numbers.length - 1;
        while (left < right) {
            int sum = numbers[left] + numbers[right];
            if (sum == target) return new int[]{left, right};
            else if (sum > target) right--;
            else left++;
        }
        return new int[]{-1, -1};
    }
}

2.4 经典示例 2:反转字符串

题目:原地反转字符串数组(O(1)O(1) 额外空间)。

Python 实现

def reverseString(s: list[str]) -> None:
    left, right = 0, len(s) - 1
    while left < right:
        # Python 优雅的元组解包交换
        s[left], s[right] = s[right], s[left]
        left += 1
        right -= 1

Java 实现

class Solution {
    public void reverseString(char[] s) {
        int left = 0, right = s.length - 1;
        while (left < right) {
            char temp = s[left];
            s[left] = s[right];
            s[right] = temp;
            left++;
            right--;
        }
    }
}

3. 同向双指针 / 快慢指针 (同侧出发,一前一后)

3.1 算法介绍

两个指针都站在起跑线(最左侧),朝着同一个方向前进,只是移动的速度或触发条件不同。

  • fast(快指针 / 探路先锋):在前面探路,通常每一步都会往前走,检查每一个元素。
  • slow(慢指针 / 大本营):走得稳。只有当“探路先锋”找到了符合条件的新元素时,慢指针才会操作并往前走。

3.2 适用场景

通常用于 数组原地覆盖/去重元素的移除/归位(如移动零),以及 链表找中点/判断环

3.3 经典示例 1:删除有序数组中的重复项

题目:原地删除重复出现的元素,使每个元素只出现一次,返回新长度。

Python 实现

def removeDuplicates(nums: list[int]) -> int:
    if not nums: return 0
    slow = 0
    # 快指针从第二个元素开始探路
    for fast in range(1, len(nums)):
        if nums[fast] != nums[slow]: # 发现不重复的新元素
            slow += 1
            nums[slow] = nums[fast]  # 接收新元素
    return slow + 1

Java 实现

class Solution {
    public int removeDuplicates(int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        int slow = 0;
        for (int fast = 1; fast < nums.length; fast++) {
            if (nums[fast] != nums[slow]) {
                slow++;
                nums[slow] = nums[fast];
            }
        }
        return slow + 1;
    }
}

3.4 经典示例 2:移动零

题目:将数组中所有 0 移动到末尾,保持非 0 元素的相对顺序。

Python 实现

def moveZeroes(nums: list[int]) -> None:
    slow = 0
    # 第一步:把非 0 元素全部按顺序挪到前面
    for fast in range(len(nums)):
        if nums[fast] != 0:
            nums[slow] = nums[fast]
            slow += 1
            
    # 第二步:剩下的位置全部填 0
    while slow < len(nums):
        nums[slow] = 0
        slow += 1

Java 实现

class Solution {
    public void moveZeroes(int[] nums) {
        int slow = 0;
        for (int fast = 0; fast < nums.length; fast++) {
            if (nums[fast] != 0) {
                nums[slow] = nums[fast];
                slow++;
            }
        }
        while (slow < nums.length) {
            nums[slow] = 0;
            slow++;
        }
    }
}

4. 复杂度总结

以上所有双指针算法的时间和空间复杂度均为:

  • 时间复杂度O(N)O(N),指针仅对数据进行单次/恒定次数的线性扫描,无嵌套循环。
  • 空间复杂度O(1)O(1),仅使用了常数级别的额外指针变量,全部为原地操作。