LeetCode刷题挑战第一题-两数之和(JS)

801 阅读1分钟

题目

两数之和(难度-简单)>>

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

示例

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

解答

题解1: 暴力枚举

var twoSum = function(nums, target) {
    let result=[]; //这里执行了1次
   for(let i=0;i<nums.length;i++){ //这里执行了n次
       for(let j=i+1;j<nums.length;j++){ //这里执行了n*n次
           if(nums[i]+nums[j] === target){ //这里执行了n*n次
               result.push(i,j)
           }
       }
   }
   return result;//这里执行了1次
};
twoSum([3,2,4],6);//输出[1,2]

解题思路:

很容易想到的是通过两层for循环,前一个和后一个依次相加是否等于target,等于就返回。

image.png

分析:

提交结果执行用时内存消耗语言
通过120 ms41.1 MBJavaScript
  • 时间复杂度:只看最大循环量,时间复杂度是T(n) = O(n²)。
  • 空间复杂度:O(1)。

题解2: 时间复杂度小于O(n²)的算法

var twoSum = function(nums, target) {
    let map = new Map();//创建一个Map
    for(let i = 0;i<nums.length;i++){//循环nums:这里执行了n次
        if(map.has(target-nums[i])){//如果map中存在target-nums[i],返回当前索引及target-nums[i]所在的索引值
            return [i,map.get(target-nums[i])]
        }
        map.set(nums[i],i);//如果不存在,将nums[i]的值作为key,索引值作为value存入map,便于map.has去对比key值
    }
};
提交结果执行用时内存消耗语言
通过72 ms41.9 MBJavaScript

解题思路

换一种思路,直接去查找target -nums[i]是否在数组中,即可少一层循环。

image.png

分析

  • 时间复杂度:O(N),其中 N 是数组中的元素数量。
  • 空间复杂度:O(N),其中 N 是数组中的元素数量。主要为哈希表的开销。(hash表,根据关键词key value直接的访问内存存储位置的数据结构。)

其他

根据题解2思路测试了其他两种写法,测试结果如下:

  1. 测试1:使用indexOf去查找数组索引,花销时间依旧很大,看起来时间复杂度为O(N),但底层实际还是使用了遍历。实际上效率并不高。
//indexOf 测试1
var twoSum = function(nums, target) {
    for(let i=0;i<nums.length;i++){
        const targeIndex = nums.indexOf(target-nums[i]);
        if(targeIndex != -1 && targeIndex != i){
            return [i,targeIndex]
        }
    }
};
//indexOf的实现
 Array.prototype.indexOf = (function(Object, max, min) {
    "use strict"
    return function indexOf(member, fromIndex) {
      if (this === null || this === undefined)
        throw TypeError("Array.prototype.indexOf called on null or undefined")

      var that = Object(this), Len = that.length >>> 0, i = min(fromIndex | 0, Len)
      if (i < 0) i = max(0, Len + i)
      else if (i >= Len) return -1

      if (member === void 0) {        // undefined
        for (; i !== Len; ++i) if (that[i] === void 0 && i in that) return i
      } else if (member !== member) { // NaN
        return -1 // Since NaN !== NaN, it will never be found. Fast-path it.
      } else                          // all else
        for (; i !== Len; ++i) if (that[i] === member) return i

      return -1 // if the value was not found, then return -1
    }
  })(Object, Math.max, Math.min)
提交结果执行用时内存消耗语言
通过164 ms41.7 MBJavaScript
  1. 测试2:还是利用hash表,根据关键词key直接的访问到对应的value,查找效率比较高。和题解2中原理是一致的。
//测试2
var twoSum = function(nums, target) {
    let map = {};
    for(let i=0;i<nums.length;i++){
     if(map[target-nums[i]] !== undefined){
         return [i,map[target-nums[i]]]
     }
     map[nums[i]] = i;
    }
    return []
};
提交结果执行用时内存消耗语言
通过64 ms41.5 MBJavaScript

算法复杂度

时间复杂度T(n) = O(f(n))

时间复杂度参考代码执行时间的趋势,并不代表代码执行的实际时间,通常只取最大循环量去衡量。 常见的时间复杂度:

  • 常数阶:O(1)
  • 线性阶:O(n),数据量增大n倍时,耗时也增大n的n倍
  • 平方阶:O(n^2)
  • 立方阶:O(n^3)
  • 对数阶:O(logn)。如二分查找,n以对数的规模进行递减 n->n/2->n/4
  • 线性对数阶:O(nlogn)
  • 指数阶:O(2^n) 优劣对比:O(1)<O(logn)<O(n)<O(nlogn)<O(n^ 2)< O(n^ 3)<O(2^ n)

空间复杂度S(n) = O(f(n))

空间复杂度是参考代码执行占用内存的趋势,并不指实际占用空间,而是计算整个算法的辅助空间变量个数。

在编写算法时,若有两者需要取舍的方案,大多以空间换取时间。