大家好,我是你们的算法小伙伴。今天我们来练习一道双指针的经典高频题 ------LeetCode 167. 两数之和 II - 输入有序数组。这道题是「两数之和」的进阶版本,利用数组有序的特性可以实现时间和空间的双重优化,是面试中考察双指针思想的标杆题目。
题目描述
给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。
以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。
你可以假设每个输入只对应唯一的答案 ,而且你不可以重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
示例 1:
输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
示例 2:
输入:numbers = [2,3,4], target = 6
输出:[1,3]
解释:2 与 4 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3] 。
示例 3:
输入:numbers = [-1,0], target = -1
输出:[1,2]
解释:-1 与 0 之和等于目标数 -1 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
提示:
2 <= numbers.length <= 3 * 10^4-1000 <= numbers[i] <= 1000numbers按 非递减顺序 排列-1000 <= target <= 1000- 仅存在一个有效答案
解题思路
核心特性拆解
数组的核心特性:非递减有序 + 唯一答案 + 常量空间限制。
- 无序数组的两数之和用哈希表(时间 O (n),空间 O (n)),但本题要求常量空间,且数组有序,因此可以用双指针实现 O (1) 空间。
- 双指针的核心逻辑:
- 左指针
left指向数组开头(最小元素),右指针right指向数组末尾(最大元素); - 计算两数之和
sum = numbers[left] + numbers[right]:- 若
sum == target:找到答案,返回[left+1, right+1](题目下标从 1 开始); - 若
sum < target:和太小,需要增大和,因此左指针右移(left++); - 若
sum > target:和太大,需要减小和,因此右指针左移(right--);
- 若
- 由于数组有序,指针移动的方向唯一,不会错过正确答案,且保证时间复杂度 O (n)。
- 左指针
方法一:双指针法(最优解,符合空间要求)
思路:利用数组有序的特性,双指针从两端向中间遍历,一次遍历即可找到答案,空间复杂度 O (1),完美满足题目要求。
方法二:二分查找法(补充思路)
思路:遍历数组中的每个元素 numbers[i],计算目标补数 complement = target - numbers[i],然后在 i 之后的数组中用二分查找补数。时间复杂度 O (n log n),空间复杂度 O (1),但效率低于双指针法。
代码实现
方法一:双指针法(最优解)
class Solution {
public int[] twoSum(int[] numbers, int target) {
int left = 0;
int right = numbers.length - 1;
while (left < right) {
int sum = numbers[left] + numbers[right];
if (sum == target) {
// 题目下标从1开始,因此+1
return new int[]{left + 1, right + 1};
} else if (sum < target) {
// 和太小,左指针右移
left++;
} else {
// 和太大,右指针左移
right--;
}
}
// 题目保证有唯一答案,因此不会走到这里
return new int[]{-1, -1};
}
}
方法二:二分查找法(补充)
class Solution {
public int[] twoSum(int[] numbers, int target) {
int n = numbers.length;
for (int i = 0; i < n; i++) {
int complement = target - numbers[i];
// 在i之后的区间二分查找补数
int left = i + 1, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (numbers[mid] == complement) {
return new int[]{i + 1, mid + 1};
} else if (numbers[mid] < complement) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return new int[]{-1, -1};
}
}
代码详解
一、双指针法
- 指针初始化 :
left = 0(数组开头),right = numbers.length - 1(数组末尾)。 - 遍历逻辑 :
- 每次计算两数之和,根据和与目标的大小关系移动指针;
- 数组非递减的特性保证了:左指针右移只会增大和,右指针左移只会减小和,因此不会错过正确答案;
- 下标处理:题目要求下标从 1 开始,因此返回时给两个指针都 + 1。
示例 1 模拟 :numbers = [2,7,11,15], target = 9
left=0, right=3:sum=2+15=17 > 9→right=2left=0, right=2:sum=2+11=13 > 9→right=1left=0, right=1:sum=2+7=9 == 9→ 返回[1,2]
二、二分查找法
- 遍历每个元素 :以
numbers[i]为第一个数,计算需要的补数complement = target - numbers[i]; - 二分查找补数 :在
i+1到末尾的区间内二分查找补数,找到则返回下标; - 优缺点:空间复杂度同样 O (1),但时间复杂度 O (n log n),不如双指针法高效,仅作为补充思路。
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 双指针法 | O(n) |
O(1) |
时间最优,空间最优,完全符合题目要求 | 无 |
| 二分查找法 | O(n log n) |
O(1) |
空间符合要求 | 时间效率低于双指针法 |
总结
- 核心考点 :本题的核心是利用数组有序的特性,用双指针将时间复杂度从哈希表的 O (n)(空间 O (n))优化为 O (n)(空间 O (1)),完美满足题目常量空间的要求。
- 面试优先级:双指针法是本题的最优解,面试中必须优先写出,代码简洁、逻辑清晰,几乎零出错。
- 拓展对比 :和「两数之和」(无序数组)的区别:
- 无序数组:只能用哈希表(时间 O (n),空间 O (n));
- 有序数组:双指针法(时间 O (n),空间 O (1)),是更优的解法。
- 易错点:题目下标从 1 开始,返回时必须给指针 + 1,否则会直接报错。
今天的每日算法练习就到这里,我们明天再见!👋