leetcode-两数之和

350 阅读4分钟

这是我参与更文挑战的第1天,活动详情查看: 更文挑战

好久没刷算法题了,上一次应该还是在PKU和HDU的OJ上做的。很早就听说过leetcode,但是也没上去做过,最近工作上会有一些闲暇的时间,想着每周都上去做几题,一方面可以保持思维活跃,另一方面也算是对平时工作做的一些磨刀准备,有比较多的积累才能在合适的地方用上。之前刷算法题只想着刷更多的题,学习更多,这次侧重点会不一样,希望每刷1题能够把思路和过程清晰的表达出来,让之前不了解的其他人可以看明白,因为这段时间在工作中越来越感受到,会解决问题很重要,会把解决的过程清晰表述出来同样重要。

程序猿接触一门新的变成语言时,习惯会用一个“Hello World”来尝试,在leecode的“Hello World”应该就是这题,两数之和

题目

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target的那两个整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

类似的题,我在其他OJ上做过,所以一下子就写出来,不过还是就当第一次做,一步步讲一下思维的过程。

暴力求解

一般来说,我的习惯的,拿到题目后,先想想暴力求解的话应该怎么做。因为暴力求解在小数据量时,时间和空间都是富裕的,用暴力求解的方式对比示例的输入输出,可以验证自己对题目的理解是否正确。 这题的暴力求解方法我想到的是遍历整个数组,两两求和,然后跟target去对比是否相等,相等的就是答案,这样在最坏的情况下,需要计算n*(n-1)/2次,时间复杂度O(N^2),空间没有带来额外的复杂度,为O(N)

暴力求解.png

哈希表

暴力求解验证了我们对题目的理解后,接下来就想一些优化方案。上述方案主要问题在于两两求和的时间复杂度是O(N^2),我们要想办法降低。求和的反向是求差,我们已知target,其实可以计算出来每个数匹配的数值是多少,然后在原始数组中查询是否存在这个匹配的数就好了,由于查询可以用哈希表做到O(1)的查询,所以这个方法的时间复杂度就是遍历数组计算匹配值的O(N)。 具体步骤:

  1. 把原始数组放入hash表中
  2. target减去数组里面的每个数,得到每个数可以匹配的目标数值的列表
  3. 再从hash表中查询是否存在匹配的数

哈希表.png

常数优化

因为在第1步和第2步都是遍历数组,可以一起进行;第3步查找也可以边做第1步边查找,因为如果有答案A和B,遍历到A的时候B还未被放入,那么遍历到B的时候也一定会找到A,所以可以边放入边查找。这样第1步和第2步并不是一定会完全进行完成,这里会有1个常数时间的优化。

其他

要返回的是一个数组的下标,所以第1步的结果需要存储在一个HashMap中,键是匹配的数值,值是数组下标;如果返回的是数组中的数值而不是下标的话,可以用HashSet来存储 题目中未规定返回数组的顺序,如果要保证从小到大的话,要把从HashMap中取出来的下标放在前面,因为遍历的时候有顺序,即处理下标i的时候,HashMap中有下标0~i-1的,一定比i要小。所以把i放在后面即可

Java版本代码

class Solution {
    public int[] twoSum(int[] nums, int target) {
        HashMap<Integer, Integer> map = new HashMap();
        for(int i = 0; i < nums.length; i++) {
            Integer otherKey = target - nums[i];
            if (map.containsKey(otherKey)) {
                return new int[]{map.get(otherKey), i};
            }
            map.put(nums[i], i);
        }
        // 上面没返回,就是未找到答案
        return null;
    }
}