题目链接: leetcode-cn.com/problems/tw…
两数相加是进入LeetCode必刷的入门题, 题意为给你一个数组nums, 并给定一个目标值target, 寻找数组nums中哪两个数字相加的和为target, 若有则输出这两个数字在数组nums的下标.
常规解法O(n^2)
题目保证了"一定会有两个数字满足相加等于target"且"不用考虑输出答案下标的顺序". 所以这道题本身不难, 做一个双重循环, 遍历数组即可. 时间复杂度为O(n^2), 空间复杂度为O(1). 出题者只是想让大家熟悉一下LeetCode的答题模式.
var x,y;
nums.forEach((a,i) => {
nums.forEach((b,j) => {
if (a+b === target && i !== j) {
x = i;
y = j;
}
})
})
return [x,y]
进阶 O(nlogn)
但这样就满足了吗? 当然不可能, 题目里都说了还有一种进阶之法, 既然看到了岂会有不做之理?!
让我们分析一下如何优化? 数组肯定至少遍历一遍这个操作是没跑了, 那么我们遍历一遍数组的时候能得到什么呢? 如nums=[a,b,c], 我们可以得到的是[target-a, target-b, target-c]这三个值,所以我们遍历数组的时候就可以快速知道当前遍历的值a和与之对应的值b=target-a. 接下来就转为如何快速的找到这个b以及它的位置. 对于快速找一个元素在数组中的位置我们就很容易想到将元素进行排序, 排序后的数组就有规律进行快速查找了.
排序的时间复杂度为O(nlogn). 这样我们就毫无压力的拿到了一个排序号的数据, 时间也比以前短.
那么我们怎么对数组进行遍历呢? 首先我们知道target为两个数字相加, 通常是一个小一点的数字+大一点的数字(当然, 两个相等的值相加也可以), 嗯... 这是不是和我们排序就关联起来了呢?
假设我们数组[a1, a2, a3, a4, a5]是一个增序的数组(由小到大), 那么我们当最小的数字a1+a5>target时, 就说明需要找一个更小的数字和a1进行相加了, 那肯定是要看a1+a4是否等于target了. 因为不会有数字比a1还小, 说明a5肯定不在最终答案里. 这样数组就变为了[a1, a2, a3, a4]. 若a1+a4<target, 则说明需要一个比a1大的数字a2才有可能与a4相加才可能为target. 这样数组就变为[a2, a3, a4], 以此类推, 知道ax+ay=target, 这时候他们原本的下标即为我们的答案.
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
otherNums = nums.map((x, idx) => ({value: target-x, pos: idx}));
otherNums.sort((a,b) => a.value - b.value);
const maxLen = nums.length;
let i = 0, j = maxLen-1;
while(i < j) {
sum = otherNums[i].value + otherNums[j].value
if (sum === target) {
return [otherNums[i].pos, otherNums[j].pos];
}
if (sum < target) {
i++;
} else {
j--;
}
}
};
耗时:
提升了4倍的速度还是很明显的.
扩展思考 O(n)
除此之外, 还有另一种空间换时间的方法, 我们既然遍历数组的时候知道了当前遍历值, 遍历值的下标和需要的值, 只需要知道需要值在不在数组里, 如果在的话知道下标就可以了. 这是不是很像一个新的数组(或者说是对象), 将原来的值作为下标, 将原来的下标作为值, 就能O(1)的时间找到值了. 如原数组为[1,4,6,2], 新的数组为[undefined, 0, 3, undefined, 1, undefined, 2]. 这样就能快速找到了, 但这里有个缺陷是当原数组为负数时, 就需要进行二次处理了, 至于处理方式留给你们去解决