leetcode_167. 两数之和 II - 输入有序数组

166 阅读1分钟

「这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」。

题目链接: 167. 两数之和 II - 输入有序数组

题目

给你一个下标从 1 开始的整数数组numbers,该数组已按非递减顺序排列,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1]numbers[index2],则 1 <= index1 < index2 <= numbers.length

以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1index2

你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。

你所设计的解决方案必须只使用常量级的额外空间。

示例 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
  • 仅存在一个有效答案

代码模板

/**
 * @param {number[]} numbers
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function (numbers, target) {};

解法

首先看到这道题最应该关注的是被加粗的关键词,比如非递减顺序排列,像这个非递减到底是怎么改非递减法呢?是不是就是递增?这个时候就需要一个个小小的测试看一下了

设计部分递减,测试用例如下

输入:numbers = [1,3,2,4], target = 5

最终结果如下图

25.png

所以非递减就是递增?可能是数组中存在重复的元素不算是递增?就像下面的测试例子一样

下面这个测试用例可以通过 leetcode 的测试

输入:numbers = [1,3,3,4], target = 5

那么确立了非递增的概念之后我们可以继续分析题目,另外两个关键词

  1. 只对应唯一的答案
  2. 不可以重复使用相同的元素

第二个好判断,就是答案不能使用数组中索引相同的元素(这个就算你返回了也不是 leetcode 对应的答案)

只对应唯一答案,就是不可以有两个答案和没有答案,注意小细节测试用例没有答案也是不行的,像下面这个测试用例是不能够通过的

输入:numbers = [1,2,3,4], target = 8

26.png

搞清楚上面这些定义以后就可以实操题目了,找出满足相加之和等于目标数 target 的两个数,还是一个数组!作为 for 循环集大成者,所有检索我都先暴力一波 for 循环,甭管它什么简单题中等题困难题,你要是找两个数我就两个 for 循环

这个解法我信手捏来,代码如下

var twoSum = function (numbers, target) {
  const res = [-1, -1];
  for (let i = 0; i < numbers.length; i++) {
    res[0] = i;
    for (let j = i + 1; j < numbers.length; j++) {
      if (target - numbers[res[0]] === numbers[j]) {
        res[1] = j;
        // 下标需为 1 开始
        res[0] = res[0] + 1;
        res[1] = res[1] + 1;
        return res;
      }
    }
  }
  return res;
};

它的解法思路如下,就是求数组的两两组合的次数的过程中判断是否有满足条件的,小学数学啦

27.png

时间复杂度在 O(n^2),不用想了这个复杂度一般都过不了的

但是小细节了家人们,这可是一个非递增序的数组呀,就是有没有一种可能我们能够找到一种规律去优化检索算法呢?

左右指针解法

答案就是双指针,准确来说是左右指针,在一次遍历数组的情况下帮助我们去缩小范围,其思路如下

28.png

number[j] + number[i] > target 时,右指针左移,原因是

number[n] 为递增数组
所以 number[i+1] > number[i], number[j] > number[j-1]

则有
number[i+1] + number[j] > target
number[i] + number[j-1] 有可能 > 或 = 或 < target

为了找到满足条件的数组元素,应该使右指针左移

同理,当 number[j] + number[i] < target

必然有
number[i] + number[j-1] < target

为了找到满足条件的数组元素,应该使左指针右移

因此这道题的解法为

var twoSum = function (numbers, target) {
  let left = 0,
    right = numbers.length;
  while (left < right) {
    const sum = numbers[left] + numbers[right];
    if (sum === target) {
      return [left + 1, right + 1];
    } else if (sum < target) {
      left++;
    } else {
      right--;
    }
  }
  return [-1, -1];
};

拓展-哈希求解

其实 O(n) 的复杂度用哈希也是能够求解的,但是设置 Map 的空间复杂度达到了 O(n)(最坏的情况下)因此不满足这道题的条件,但是在这道题的 mini 版,1. 两数之和(leetcode-cn.com/problems/tw…

var twoSum = function (numbers, target) {
  let numsMap = new Map();
  for (let i = 0; i < numbers.length; i++) {
    if (numsMap.has(target - numbers[i])) {
      return [numsMap.get(target - numbers[i]), i];
    }
    numsMap.set(nums[i], i);
  }
};