今天做了这道题,本来是很简单的一个题,但是能知道自己在思路上与厉害的人的差距。
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
这道题的意思就是说给定一个数,它是一个数组里面两个数的和,返回这两个索引即可。每个数组里面的数只能用一次且答案只有一个都让这道题变得简单了。
最先想到的肯定是暴力枚举,选择数组里面一次取两个数,所有不重复的可能之和如果等于给定数,返回这两个数的索引。思路很简单,代码也很简单。这个方法的时间复杂度很明显是O(n^2),肯定不是最好的选择。所以还是要继续向下想。
var twoSum = function(nums, target) {
// method 1 暴力法 每个i都选择i + 1之后的可能性,之前的一定重复
for(let i = 0; i < nums.length; i ++) {
for (let j = i + 1; j < nums.length; j ++) {
if(nums[i] + nums[j] == target) {
return [i, j];
}
}
}
}
继续思考的话,就需要转变一下想法,对于每一个数 nums[i]
我们并不是盲目的找别的数,它是一个确定的数 target - nums[i]
,也就是说我们只需要找到这个数 target - nums[i]
在数组里面的是否存在,存在的话返回索引就成功了。这样其实问题已经从数组里面找一对数变成了固定一个数从数组里面找另一个确定的数。第一次遍历是固定的,关键是减少第二个数找的时间,暴力枚举是从数组里面遍历索引找第二个数 ,那我们有没有更好的方法去找第二个数。答案是肯定的。我们通过数去找索引,如果能一下找到第二个数,那索引也是直接找到的。
那就是哈希表,也叫散列表。通过一定的计算可能直接找到键,时间复杂性是O(1)。我们这里使用Map结构把数当成键,索引当成值。如果能直接找到第二个数存在,取出这个键的值就是我们想要的索引了。
var twoSum = function(nums, target) {
// method 2 哈希表两次
let map = new Map();
for(let i = 0; i < nums.length; i ++) {
map.set(nums[i], i);
}
for(let i = 0; i < nums.length; i ++) {
if(map.has(target - nums[i]) && map.get(target - nums[i]) != i) {
return [i, map.get(target - nums[i])];
}
}
};
思路很清晰了,就是最先把数组里面的数当成键,索引当成值,放到一个Map里面。之后只需要一个循环,每次检查一下第二个数 target - nums[i]
在不在Map里面,在就找到,马上返回Map里面对应的索引就成功了。
其实还可以再次优化,上面这里面循环还可以变成一个循环,即一边向Map结构里面放数据,一边检查第二个数是不是存在。因为两个数放入Map结构一定有先后顺序,那第一个放入Map结构的数,在轮到第二个数的时候,一定能在Map结构里面找到。需要注意的是在Map里面放入的数一定是索引小的数。所以返回的时候需要注意一下,i
要放到返回数组的后面。
var twoSum = function(nums, target) {
// method 3 哈希表一次
let map = new Map();
for(let i = 0; i < nums.length; i ++) {
if(map.has(target - nums[i]) && map.get(target - nums[i]) != i) {
return [map.get(target - nums[i]), i];
}
map.set(nums[i], i);
}
};
这就是终级版代码的样子了,全部是用js写的。
这次的问题告诉我们:在数组里面找一个数的问题可以转换成在哈希表,在O(1)的时间就可以找到