给定一个已按照 升序排列 的整数数组 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 。
方法一:参考题目两数之和,使用暴力法or哈希表,但未用到数组升序排列的优势
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
map<int, int> mapNums;
for(int i = 0; i < numbers.size(); i++) {
if(mapNums.count(target-numbers[i]) == 1)
return {mapNums[target-numbers[i]]+1, i+1};
else
mapNums[numbers[i]] = i;
}
return {0,0};
}
};
错误记录:for判断时加了条件numbers[i]<target,本意是若数组值大于target的话就结束循环,提高效率,但未考虑数组中存在负数的情况,target比数组元素小的情况。
下标从1开始计数不等于从numbers[1]开始存放数据。
方法二:二分查找法
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
for(int i = 0; i < numbers.size(); i++) {
int low = i+1, high = numbers.size()-1; //在numbers[i]的右边查找
while(low <= high) {
int mid = (high-low)/2 + low;
if(numbers[mid] == target - numbers[i]) //找到
return {i+1, mid+1};
//中间值比结果值小,继续在右半边查找,重置low,high不需要
else if(numbers[mid] < target - numbers[i])
low = mid+1;
else //中间值比结果值大,则在左半边查找
high = mid-1;
}
}
return {-1,-1};
}
};
时间复杂度:O(nlogn)其中n是数组的长度。需要遍历数组一次确定第一个数,时间复杂度是 O(n),寻找第二个数使用二分查找,时间复杂度是 O(logn),因此总时间复杂度是 O(nlogn)。
空间复杂度:O(1)。
算法知识
二分查找:一种在有序数组中查找某一特定元素的搜索算法,每次比较都使搜索范围缩小一半。
搜索过程从数组的中间元素开始,若中间元素正好是要查找的元素,则搜索过程结束;若查找元素小于中间元素,则在数组左半边查找;若查找元素大于中间元素,则在数组右半边查找。如此反复,知道数组为空,则代表找不到。
方法三:双指针
数组首尾设置两个标志low和high,判断numbers[low]+numbers[high]是否等于target,若相等则返回;若小于target,此时high在最高位,数组有序,说明numbers[high]已经是最大值,若要增加numbers[low]+numbers[high]的和,只能增加numbers[low],因此low++进行判断;同理若大于target,此时要减少sum,因此high--。leetcode解题法有大神详细图解了这种缩减搜索空间的思路。
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int low = 0, high = numbers.size()-1;
while(low < high) {
int sum = numbers[low] + numbers[high];
if(sum == target)
return {low+1, high+1};
else if(sum < target)
low++;
else
high--;
}
return {-1,-1};
}
};