题目
给定一个 升序排列 的整数数组 numbers,请你从数组中找出两个数满足相加之和等于目标数 target。函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 1 开始计数 ,所以答案数组应当满足 1 <= answer[0] < answer[1] <= numbers.length。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例 1:
输入:numbers = [2,7,11,15], target = 9 输出:[1,2] 解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
示例 2:
输入:numbers = [2,3,4], target = 6 输出:[1,3]
示例 3:
输入:numbers = [-1,0], target = -1 输出:[1,2]
📖 文字题解
这道题可以使用「1. 两数之和」的解法,使用 的时间复杂度和
的空间复杂度暴力求解,或者借助哈希表使用
的时间复杂度和
的空间复杂度求解。但是这两种解法都是针对 无序数组 的,没有利用到 输入数组有序 的性质。利用输入数组有序的性质,可以得到时间复杂度和空间复杂度更优的解法。
解题思路:二分查找
在数组中找到两个数,使得它们的和等于目标值,可以首先固定第一个数,然后寻找第二个数,第二个数等于目标值减去第一个数的差。
- 利用数组的有序性质,可以通过 二分查找 的方法寻找第二个数。
- 为了避免重复寻找,在寻找第二个数时,只在第一个数的 右侧 寻找。
复杂度分析
时间复杂度:,其中
为数组长度。
- 需要遍历数组一次确定第一个数,时间复杂度是 ,寻找第二个数使用二分查找,时间复杂度是 ,因此总时间复杂度是  。
空间复杂度:
官方代码
class Solution {
public int[] twoSum(int[] numbers, int target) {
for (int i = 0; i < numbers.length; ++i) {
//二分查找
int low = i + 1, high = numbers.length - 1;
while (low <= high) {
int mid = (high - low) / 2 + low;
if (numbers[mid] == target - numbers[i]) {
return new int[]{i + 1, mid + 1};
} else if (numbers[mid] > target - numbers[i]) {
high = mid - 1;
} else {
low = mid + 1;
}
}
}
return new int[]{-1, -1};
}
}
解题思路:滑动窗口 [ 双指针 ]
初始时两个指针分别指向第一个元素位置和最后一个元素的位置。每次计算两个指针指向的两个元素之和,并和目标值比较。如果两个元素之和等于目标值,则发现了唯一解。
- 如果两个元素之和小于目标值,则将左侧指针右移一位。
- 如果两个元素之和大于目标值,则将右侧指针左移一位。
- 移动指针之后,重复上述操作,直到找到答案。
使用双指针的实质是缩小查找范围。那么会不会把可能的解过滤掉?答案是不会。
解释1:
我们知道一开始的时候,,
,
是区域最小值、
是区域最大值
- 如果
,一定是
左移,因为
加上 区域最小值 还比
大,只能减小
- 如果
,一定是
右移, 因为
加上 区域最大值 还比
小,只能增加
那么整体的区间会由开始慢慢缩小,直至找到最终答案
解释2:
假设 是唯一解,其中
。初始时两个指针分别指向下标
和下标
,左指针指向的下标小于或等于
,右指针指向的下标大于或等于
。除非初始时左指针和右指针已经位于下标
和
,否则一定是左指针先到达下标
的位置或者右指针先到达下标
的位置。
- 如果左指针先到达下标
的位置,此时右指针还在下标
的右侧,
sum>target,一定是右指针左移,左指针不可能移到的右侧。
- 如果右指针先到达下标
的位置,此时左指针还在下标
的左侧,
sum<target,一定是左指针右移,右指针不可能移到的左侧。
由此可见,在整个移动过程中,左指针不可能移到 的右侧,右指针不可能移到
的左侧,因此不会把可能的解过滤掉。由于题目确保有唯一的答案,因此使用双指针一定可以找到答案。
复杂度分析
时间复杂度:,其中
为数组长度,两个指针移动的总次数最多为
次
空间复杂度:
官方代码
class Solution {
public int[] twoSum(int[] numbers, int target) {
int low = 0, high = numbers.length - 1;
while (low < high) {
int sum = numbers[low] + numbers[high];
if (sum == target) {
return new int[]{low + 1, high + 1};
} else if (sum < target) {
++low;
} else {
--high;
}
}
return new int[]{-1, -1};
}
}
