题目
给你一个下标从 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 * 104
- -1000 <= numbers[i] <= 1000
- numbers 按 非递减顺序 排列
- -1000 <= target <= 1000
- 仅存在一个有效答案
解题思路
哈希表解法
一开始没看到使用常量级的额外空间的要求,所以直接使用了哈希表来存储每个元素的下标。遍历数组的同时查找哈希表中是否存在满足条件的元素。
1 | func twoSum(numbers []int, target int) []int { |
暴力解法
后来看到题目要求使用常量级的额外空间,所以直接暴力解法也是可以的:
1 | func twoSum(numbers []int, target int) []int { |
这里当 x > target
时直接跳出内层循环,因为数组是有序的,后面的元素一定会更大。并且由于数组是有序的,可以考虑在查找时使用二分查找来提高效率。
二分查找法
1 | func binarySearch(target int, numbers []int) int { |
这样时间复杂度为 O(nlogn)
,空间复杂度为 O(1)
。提交后耗时为 0ms
,内存为 7.63MB
。
双指针法
上面的方法,都是遍历数组的每一个数,根据每一个数和目标值的差值来查找另一个数。而由于数组是有序的,所以用二分查找优化了查找的时间复杂度。但其实有部分查找是重复的,如果考虑优化这部分重复,就要用空间换时间,记录每一个元素和目标值的差值。但是这样就会增加空间复杂度。如果要想同时降低时间复杂度和空间复杂度,就要从另一个角度考虑问题 缩减查找的范围:
- 初始的搜索范围是
i := 0
到j := len(numbers)-1
,即整个数组。 - 如果
numbers[i] + numbers[j] == target
,则直接返回i
和j
。 - 如果
numbers[i] + numbers[j] < target
,则说明numbers[i] + numbers[j]
不够大,可以进一步压缩范围,即i++
。 - 如果
numbers[i] + numbers[j] > target
,则说明需要减小numbers[i] + numbers[j]
,可以进一步压缩范围,即j--
。
所以可以使用双指针法来实现,时间复杂度为 O(n)
,空间复杂度为 O(1)
。
1 | func twoSum(numbers []int, target int) []int { |