[哈希映射、二分] 1. 两数之和

150 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情

每日刷题 2022.06.06

题目

  • 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。
  • 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
  • 你可以按任意顺序返回答案。

示例

  • 示例1
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1]
  • 示例2
输入: nums = [3,2,4], target = 6
输出: [1,2]
  • 示例3
输入: nums = [3,3], target = 6
输出: [0,1]

提示

  • 2 <= nums.length <= 10^4
  • -10^9 <= nums[i] <= 10^9
  • -10^9 <= target <= 10^9
  • 只会存在一个有效答案
  • 进阶:你可以想出一个时间复杂度小于 O(n2) 的算法吗?

解题思路

  • !!每种输出只会对应一个答案!!

第一种做法:二分

  • 分析:在nums数组两个数,加和起来等于target,返回这两个数对应的下标。注意⚠️:同一个数只能使用一次
    • 同一个数,表示的是下标相同值也相同的数不能一起使用,下标不同但是值相同是可以一起使用的。
  • 常规的做法:对于数组nums中的每一个位置i,都向后遍历j,查找nums[i] + nums[j] = target的元素。
    • 分析时间复杂度:o(n ^ 2)
  • 进阶的做法:固定一个元素查找另一个元素,且数组nums可以排序成为有序数组,那么就可以使用二分查找,来查找到另一个元素。
    • 因为二分查找的时间复杂度:o(logn),再加上我们最坏要对数组nums中的每个数都遍历一遍,总的时间复杂度o(n logn)

技巧

  • 因为对nums数组进行排序后,原本的下标就会被打乱,而我们需要返回的就是原本的下标,想着能不能使用map来存储下标和值之间的映射关系,发现不太行,因为数组nums中会存在多个相同的值,并不是一一对应的,因此排除掉map
  • 后来就想到了使用自定义排序,如果我们创建一个新的数组idx,将其按照nums数组中的值的大小进行排序,那么就可以得到nums排序后每个位置对应的下标,形成一一对应。
  • 举例
         nums = [3, 2, 4]
       对应的下标:0, 1, 2
创建新的数组idx = [0, 1, 2]
对idx数组按照nums数组中的数值大小进行自定义升序排序后:
          idx = [1, 0, 2]
再对nums数组进行升序排序后:
         nums = [2, 3, 4]
观察此时的idx和nums数组,nums数组中的值对应的唯一的下标就一一对应在idx数组中

第二种做法:哈希映射

  • 并不是一开始就将所有的数组nums中的值和下标对应的关系存储到map集合中,而是将数组nums中位置i之前的存入到map集合中。
  • 这种做法也是比较巧妙的,对于数组nums的每一个元素nums[i]往前查找有没有符合target - nums[i]的,使用map数据结构存储:当前位置i前面的值和下标的对应关系,直接从map中查找即可。
    • 两个相同的数但不同下标,3 + 3 = 6这样的,当前位于i,前面或者后面寻找一个即可,不会被覆盖
  • 这种做法只适用于两数之和,如果是三数之和就不行了,因为map中的元素并不是一一对应的,可能会存在重复覆盖的情况
  • 举例
nums = [3,5,3,3], target = 9
// 此时使用map集合记录每个数值对应的下标的时候,就会被后面来的覆盖掉

AC代码

  • 第一种做法:二分查找
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
  // nlogn
  // console.log(nums)
  let n = nums.length, ans = new Array(2);
  let index = new Array(n);
  for(let i = 0; i < n; i++) {
    index[i] = i;
  }
  // console.log(index)
  index.sort((a, b) => {
    return nums[a] - nums[b];
  });
  // console.log(index)
  nums.sort((a, b) => a - b);
  function binary (t) {
    // 返回值是找的匹配的下标
    let l = -1, r = n;
    while(l + 1 != r) {
      let mid = l + parseInt((r - l) / 2);
      // console.log(l,r,mid)
      if(nums[mid] < t){
        l = mid;
      }else {
        r = mid;
      }
    }
    return r;
  }
  for(let i = 0; i < n; i++) {
    let tempt = target - nums[i];
    // 小于的时候执行
    let idx = binary(tempt);
    // 如果没有找到
    // console.log(i,nums[i], idx)
    if(idx != n && nums[idx] == tempt && idx != i) {
      ans[0] = index[i], ans[1] = index[idx];
      return ans;
    }
  }
  return ans;
};
  • 第二种做法:哈希映射
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
  let map = new Map(), n = nums.length;
  for(let i = 0; i < n; i++) {
    let cha = target - nums[i];
    if(map.has(cha)) {
      return [map.get(cha), i];
    }
    map.set(nums[i], i);
  }
};

总结

  • 简单的题目,要更注重思考的过程