LeetCode 167. 两数之和 II - 输入有序数组:双指针解法详解

20 阅读4分钟

在 LeetCode 的数组类题目中,“两数之和”系列是经典入门题,而第 167 题在基础版之上增加了“数组有序”的条件,这一条件为我们提供了更高效的解法思路。本文将拆解这道题的核心考点,详细讲解双指针解法的原理、代码实现及优化逻辑,帮助大家理解为何有序数组能催生更优解。

一、题目核心分析

题目要求

给定一个下标从 1 开始、非递减排列的整数数组 numbers,找出两个数使其和等于目标值 target。返回这两个数的下标(1-based,且 index1 < index2),保证输入仅有唯一答案,且不可重复使用相同元素。额外限制:解决方案必须使用常量级额外空间(O(1) 空间复杂度)。

关键考点

  1. 有序数组的特性利用:非递减排列的数组是解题的核心突破口,区别于基础版两数之和的哈希表解法;

  2. 空间复杂度限制:排除哈希表(O(n) 空间),需寻找原地解法;

  3. 下标合法性:返回结果需转换为 1-based 索引,且保证顺序正确。

二、解法思路:双指针法

原理推导

有序数组的非递减特性,让我们可以通过“两端逼近”的方式定位目标数对:

  1. 初始化两个指针:左指针(leftPoint)指向数组起始位置(下标 0),右指针(rightPoint)指向数组末尾(下标 n-1);

  2. 计算两指针指向元素的和(sum),与目标值 target 比较:

  • 若 sum < target:说明当前和偏小,需增大较小数(左指针右移,因为数组递增,右移后元素更大);

  • 若 sum > target:说明当前和偏大,需减小较大数(右指针左移,左移后元素更小);

  • 若 sum == target:找到唯一答案,将指针下标转换为 1-based 后返回。

由于题目保证有唯一答案,循环过程中一定会找到符合条件的数对,无需额外处理无结果场景(返回 [-1,-1] 仅为语法兼容)。

复杂度分析

时间复杂度:O(n),其中 n 为数组长度。左、右指针最多遍历数组一次,无嵌套循环;

空间复杂度:O(1),仅使用常数个变量(指针、和值),满足题目常量级空间要求。

三、代码解析

以下是 TypeScript 实现代码,结合注释拆解每一步逻辑:


function twoSum(numbers: number[], target: number): number[] {
  const nL = numbers.length; // 存储数组长度,避免重复获取
  let leftPoint = 0; // 左指针初始化:数组起始下标
  let rightPoint = nL - 1; // 右指针初始化:数组末尾下标

  // 循环条件:左指针小于右指针(避免重复使用同一元素)
  while (leftPoint < rightPoint) {
    const sum = numbers[rightPoint] + numbers[leftPoint]; // 计算当前两数之和
    if (target > sum) {
      leftPoint++; // 和偏小,左指针右移增大数值
    } else if (target < sum) {
      rightPoint--; // 和偏大,右指针左移减小数值
    } else {
      // 找到答案,转换为1-based下标返回
      return [leftPoint + 1, rightPoint + 1];
    }
  }

  // 题目保证有唯一答案,此句仅为语法兜底
  return [-1, -1];
};

关键细节说明

  1. 下标转换:题目要求返回 1-based 下标,而数组操作是 0-based,因此最终结果需给两个指针下标各加 1;

  2. 循环边界:leftPoint < rightPoint 确保两个指针不会指向同一元素,满足“不可重复使用相同元素”的要求;

  3. 唯一答案特性:无需考虑多组解,找到 sum == target 后可直接返回,无需继续遍历。

四、对比其他解法

1. 哈希表解法(不推荐)

基础版两数之和的哈希表解法(O(n) 时间、O(n) 空间)虽能通过本题,但不满足常量级空间要求,违背题目核心限制,仅作对比参考。

2. 二分查找解法(时间复杂度更高)

针对有序数组,也可固定一个元素,对剩余元素进行二分查找(O(n log n) 时间、O(1) 空间)。但相比双指针的 O(n) 时间,效率更低,因此双指针是本题最优解。

五、测试用例验证

结合典型用例验证代码正确性:

  • 用例 1:numbers = [2,7,11,15], target = 9 → 输出 [1,2](2+7=9,下标转换后);

  • 用例 2:numbers = [2,3,4], target = 6 → 输出 [1,3](2+4=6);

  • 用例 3:numbers = [-1,0], target = -1 → 输出 [1,2](-1+0=-1)。

以上用例均能通过代码正确返回结果,覆盖正数、负数、连续元素等场景。

六、总结

本题的核心是利用“有序数组”的特性,放弃哈希表的额外空间,采用双指针两端逼近的思路,实现 O(n) 时间、O(1) 空间的最优解。这一思路不仅适用于本题,还可迁移到其他有序数组的配对问题中(如寻找两数之差、三数之和等)。

解题关键在于理解“有序”带来的大小调节能力:左指针右移增大和值,右指针左移减小和值,通过这种精准调节快速定位目标数对,避免盲目遍历。