Two Pointers(双指针)

Two Pointers(双指针)

双指针是处理有序数组 的一大利器。核心思想是用两个指针从数组的两端(或不同位置)向中间移动,根据当前两指针所指元素的和与目标值的关系,决定移动哪一个指针,从而在 O(n) 时间内找到答案。

什么时候用双指针?

  • 数组已经排序(或者你可以先排序)
  • 问题一般涉及两个数三个数的和、差、某种关系
  • 需要去重(排序后重复元素会相邻,容易跳过)

PDF 里选了 2 道题:Two Sum II3Sum(外加一道 3Sum Closest 在示例中),我们把它们串起来讲。


1. 两数之和 II(Two Sum II, 167, Easy)

题目 :在一个已经升序排列的数组里,找两个数使得它们的和等于目标值,返回这两个数的索引(下标从 1 开始),而且假定有唯一解。

思路

因为数组有序,我们可以将两个指针分别放在最左最右

  • 如果两数之和 sum == target,直接返回;
  • 如果 sum < target,说明左边的数太小了,左指针右移,让和变大;
  • 如果 sum > target,说明右边的数太大了,右指针左移,让和变小。

每次只移动一个指针,时间复杂度 O(n)

代码

java 复制代码
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 + 1, right + 1}; // 返回1-based索引
        } else if (sum < target) {
            left++;
        } else {
            right--;
        }
    }
    return new int[]{-1, -1}; // 题目保证有解,这里仅为编译需要
}

这题是双指针最朴素的应用,也说明了双指针为什么对有序两数和有效:每次比较都能确定一个指针的移动方向,不会错过正确解。


2. 三数之和(3Sum, 15, Medium)

题目 :给你一个整数数组 nums,找出所有和为 0 的三元组,且结果中不能包含重复的三元组。

核心思路

  1. 排序:先把数组从小到大排序。
  2. 固定一个数,变成两数之和 :遍历排序后的数组,对每个 nums[i],把它当成三元组的第一个数,然后在它后面 的子数组上用双指针找两个数,使它们的和等于 -nums[i]
  3. 去重
    • 如果当前 nums[i] == nums[i-1],直接跳过,因为以这个数开头的三元组我们已经在上一轮找过了。
    • 在双指针移动时,如果找到了一个解,还要分别跳过左边和右边所有与当前解重复的元素。
  4. 双指针移动规则和 Two Sum II 完全一样。

代码

java 复制代码
public List<List<Integer>> threeSum(int[] nums) {
    List<List<Integer>> res = new ArrayList<>();
    Arrays.sort(nums);                     // 1. 排序
    int n = nums.length;
    
    for (int i = 0; i < n - 2; i++) {
        if (i > 0 && nums[i] == nums[i - 1]) continue; // 去重i
        int left = i + 1, right = n - 1;
        while (left < right) {
            int sum = nums[i] + nums[left] + nums[right];
            if (sum == 0) {
                res.add(Arrays.asList(nums[i], nums[left], nums[right]));
                // 去重left和right
                while (left < right && nums[left] == nums[left + 1]) left++;
                while (left < right && nums[right] == nums[right - 1]) right--;
                left++;
                right--;
            } else if (sum < 0) {
                left++;
            } else {
                right--;
            }
        }
    }
    return res;
}

时间复杂度O(n²),排序 O(n log n),外层循环 O(n),内层双指针 O(n)
空间复杂度O(1)(忽略存储答案的空间)。


3. 最接近的三数之和(3Sum Closest, 16, Medium)

题目 :给定一个数组 nums 和一个目标值 target,找出三个整数的和,使得这个和与 target 最接近。返回这个和(假定只有唯一解)。

思路

和 3Sum 几乎一样,区别在于:

  • 我们不需要寻找和为 0,而是寻找与 target 差值最小的和。
  • 每次计算三个数的和 sum,如果 sum 比当前记录的 res 更接近 target,就更新 res
  • 移动双指针的规则仍然是根据 sumtarget 的大小关系:
    • 如果 sum > target,我们希望和变小一点,所以右指针左移;
    • 如果 sum < target,我们希望和变大一点,所以左指针右移;
    • 如果 sum == target 直接返回(因为差值已经是 0,最接近)。

代码

java 复制代码
public int threeSumClosest(int[] nums, int target) {
    Arrays.sort(nums);
    int n = nums.length;
    int res = nums[0] + nums[1] + nums[2]; // 初始值随便取,后面会更新

    for (int i = 0; i < n - 2; i++) {
        int left = i + 1, right = n - 1;
        while (left < right) {
            int sum = nums[i] + nums[left] + nums[right];
            if (Math.abs(sum - target) < Math.abs(res - target)) {
                res = sum;                 // 更接近了,更新
            }
            if (sum > target) {
                right--;
            } else if (sum < target) {
                left++;
            } else {
                return sum;                // 完全匹配,直接返回
            }
        }
    }
    return res;
}

这就是 3Sum 的一个轻量变种,只多加了一个"距离比较"。


4. 延伸:2Sum 用哈希表

PDF 里在 Two Sum 部分还给出了一个未排序数组 的解法(原版 Two Sum,LC 1),用 HashMap

java 复制代码
public int[] twoSum(int[] nums, int target) {
    Map<Integer, Integer> map = new HashMap<>();
    for (int i = 0; i < nums.length; i++) {
        int diff = target - nums[i];
        if (map.containsKey(diff)) {
            return new int[]{map.get(diff), i};
        }
        map.put(nums[i], i);
    }
    return null;
}

但双指针专题的核心还是排序后双指针O(n) 解法,哈希表的方法更多用于无序且要求返回索引的场景。


总结:双指针在求和问题中的模板

  1. 排序(如果数组本身无序)
  2. 对外层元素进行遍历 (如 3Sum 中的 i
  3. 内层双指针
    • left = i + 1right = n - 1
    • 计算 sum = nums[i] + nums[left] + nums[right](或两个数的和)
    • 根据 sumtarget 的关系移动 leftright
  4. 去重 :排序后,相同值必然相邻,用 while 跳过重复项即可。

复杂度 :对于 k-Sum 问题,排序 O(n log n),然后用双指针将暴力 O(n^k) 降到 O(n^(k-1))

相关推荐
li1670902701 小时前
第二十五章:C++11(下)
c语言·开发语言·数据结构·c++
sali-tec1 小时前
C# 基于OpenCv的视觉工作流-章58-相机标定
图像处理·人工智能·数码相机·opencv·算法·计算机视觉
承渊政道1 小时前
【动态规划算法】(回文串问题解题框架与经典案例)
数据结构·c++·学习·算法·leetcode·动态规划·哈希算法
一水鉴天1 小时前
同构异质三表总装体系确立与入表机制闭环验证 20260502(腾讯元宝)
人工智能·算法·机器学习
AI进化营-智能译站1 小时前
ROS2 C++开发系列11-VS Code一键生成Doxygen注释|让ROS2节点文档自动跟上代码迭代
java·数据库·c++·ai
qyzm1 小时前
Codeforces Round 1073 (Div. 2)
数据结构·python·算法
jieyucx1 小时前
Go 零基础数据结构:链表的增删改查(像串珠子一样简单)
数据结构·链表·golang
bzmK1DTbd1 小时前
OpenGL与Java:JOGL库的3D图形渲染实战
java·3d·图形渲染
许彰午1 小时前
CacheSQL(四):CacheSQLClient——用一张路由表实现水平扩展
java·数据库·缓存·系统架构·政务