一天一大 leet(两数之和 II - 输入有序数组)难度:简单-Day20200720

img

题目:

给定一个已按照升序排列的有序数组,找到两个数使得它们相加之和等于目标数。

函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。

说明:

  • 返回的下标值(index1 和 index2)不是从零开始的。
  • 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。

示例:

输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。

抛砖引玉

看到这道题有点小感触,想到最开始刷 leetcode 就是被这道简单的提劝退的

印象深刻,现在还记得,第一看好简单,马上就用 indexOf 写了,然后肯定没通过,泪目

/**
 * @param {number[]} numbers
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function (numbers, target) {
  let index1 = 0,
    index2 = -1
  while (index1 < numbers.length) {
    index2 = numbers.indexOf(target - numbers[index1])
    if (index2 === -1) {
      index1++
    } else {
      return index1 < index2
        ? [index1 + 1, index2 + 1]
        : [index2 + 1, index1 + 1]
    }
  }
  return index1 < index2 ? [index1 + 1, index2 + 1] : [index2 + 1, index1 + 1]
}

现在看,那时候不仅没注意复杂度的问题,而且逻辑上也有些问题,没有考虑 target 由 numbers 有两个相同是数组成的情况


然后,重点来了,自己的代码没有通过,那去看下答案吧,看了答案,答案还没看懂,也没有注解
致使被打击,刷算法的计划搁置了快两年才能重新又拾起来。

这次刷题快坚持两个月了,也知道自己的解法和描述很多地方没有别的大佬和官方讲的专业和透彻,

之所以还坚持写下,一方面就是有了记录的习惯,自己才更换坚持,
一方面是想有小伙伴想刷时,如果能通过这些题解,不要和我之前一样,能坚持入了坑也是对自己的激励


言归正传

来看看原来看到的题解吧,之前一直看不明白,为什么要把差值存起,刷了动态规划的题,
再回来看这个就觉得好简单

抛砖引玉

  • 声明一个数组 a 记录每个 numbers 中的元素与 target 直接的差值
  • 如果:x+y=target,那么 target-x=y,target-y=x
  • 当遍历到满足要求的值直接返回,不然存贮对应的差值到 a 的对应索引位置
/**
 * @param {number[]} numbers
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function (numbers, target) {
  var a = []
  for (var i = 0, len = numbers.length; i < len; i++) {
    var tmp = target - numbers[i]
    if (a[tmp] !== undefined) return [a[tmp] + 1, i + 1]
    a[numbers[i]] = i
  }
}
  • 感觉其实如果数组换成 map 会更好理解
  • 毕竟标记数据时哈希比 index 就到的多

抛砖引玉

  • 时间和空间复制度基本差不多
/**
 * @param {number[]} numbers
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function (numbers, target) {
  let dp = new map(),
    len = numbers.length
  for (let i = 0; i < len; i++) {
    let tmp = target - numbers[i]
    if (dp.has(tmp)) {
      return [dp.get(tmp) + 1, i + 1]
    }
    dp.set(numbers[i], i)
  }
}

二分法查找

以上的解法都没有用到升序排列这个条件,其实看到有序最应该想到的就是二分法查找

  • 从 numbers 取出一个元素 numbers[i],在 numbers 中 i 之后的元素中查找 target - numbers[i]
  • 查找到之间返回,不然依次从 numbers 中取后面一个元素
/**
 * @param {number[]} numbers
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function (numbers, target) {
  let len = numbers.length,
    left = 0,
    right = len - 1,
    mid = 0
  for (let i = 0; i < len; ++i) {
    left = i + 1
    while (left <= right) {
      mid = parseInt((right - left) / 2) + left
      if (numbers[mid] == target - numbers[i]) {
        return [i + 1, mid + 1]
      } else if (numbers[mid] > target - numbers[i]) {
        right = mid - 1
      } else {
        left = mid + 1
      }
    }
  }
  return [-1, -1]
}

双指针

  • 从二分法查找中会发现,其实不用查找完二分的节点就已经知道当前这个 i 是否能满足要求
  • 那当知道这个 i 不在满足要求时就没有必要接着二分了,直接切换 i 就可以
  • 那 left 和 right 就成立两个动态的索引指针了
/**
 * @param {number[]} numbers
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function (numbers, target) {
  let left = 0,
    right = numbers.length - 1
  while (left < right) {
    // 当两个指针对应的元素和等于 target直接返回
    if (numbers[left] + numbers[right] === target) {
      return [left + 1, right + 1]
    } else if (numbers[left] + numbers[right] > target) {
      // 当和大于target,则右侧减小(较大的值减小)
      right--
    } else {
      // 当和小于target,则左侧增大(较小的值增大)
      left++
    }
  }
}

博客: 小书童博客

每天的每日一天,写的题解会同步更新到公众号一天一大 lee 栏目

公号: 坑人的小书童

坑人的小书童

猜你喜欢

转载自blog.csdn.net/gaowenju1991/article/details/107462663