题目
给定一个整数数组 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,等于就返回。
分析:
| 提交结果 | 执行用时 | 内存消耗 | 语言 |
|---|---|---|---|
| 通过 | 120 ms | 41.1 MB | JavaScript |
- 时间复杂度:只看最大循环量,时间复杂度是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 ms | 41.9 MB | JavaScript |
解题思路
换一种思路,直接去查找target -nums[i]是否在数组中,即可少一层循环。
分析
- 时间复杂度:O(N),其中 N 是数组中的元素数量。
- 空间复杂度:O(N),其中 N 是数组中的元素数量。主要为哈希表的开销。(hash表,根据关键词key value直接的访问内存存储位置的数据结构。)
其他
根据题解2思路测试了其他两种写法,测试结果如下:
- 测试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 ms | 41.7 MB | JavaScript |
- 测试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 ms | 41.5 MB | JavaScript |
算法复杂度
时间复杂度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))
空间复杂度是参考代码执行占用内存的趋势,并不指实际占用空间,而是计算整个算法的辅助空间变量个数。
在编写算法时,若有两者需要取舍的方案,大多以空间换取时间。