leetcode-第一题两数之和

初衷

先说一下初衷吧,这次找工作深深感受到不刷题的被动。即便你自己认为自己的实力没有问题,遇到问题花费一定时间也能解决,但是面试的时候就那么几道题,如果你慢慢想,时间根本来不及。我也在面试的时候自己做出了动态规划的题,但是面对一些变种或者实际写码过程中可能会有很多细节问题需要考虑,比如用什么数据结构进行缓存、边界值怎么处理等问题。一旦出现卡顿,面试官可能就会认为你的写码能力不足,这个东西百口莫辩,毕竟事实摆在那里。

而且,如果到准备找工作的时候才开始刷题,时间根本来不及,所以干脆想做个长期计划,每周刷一道题,只做简单和中等。一年也能刷五十多道题了。每道题写个博客,做个记录,也能保证刷题更高效,因为输出的时候可能才会发现很多点还有缺陷。

第一题

这道题很简单,但是也有不少细节点可以注意的。

题目:

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

 

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/two-sum

我觉得做题首先要从最简单的方法开始入手,然后逐步优化。(当然熟练之后其实就可以从更高级别开始入手了)

这道题解法很多。

解法一:暴力法

直接遍历数组,对每个元素计算能凑成target的数字大小,然后在数组中查找。

这个方法时间复杂度O(n^{2}),空间复杂度O(1)

这个方法过于暴力和简单,代码不做实现。

解法二:先排序

这是一个优化方向。对数组排序,利用有序的特性,可以做一些优化。

排序后,可以用双指针,从数组头部和尾部向中间推进。如果当前二元素之和等于target,就返回这两个指针;如果大于target,尾指针减1;如果小于target,头指针加1。

排序时间复杂度为O(n\log_2n),双指针遍历时间复杂度O(n),总的时间复杂度为O(n\log_2n)

空间复杂度为O(1)。

解法三:HashMap缓存,两次遍历

这是一个空间换时间的方法。将数组中的元素保存在HashMap里,元素值作key,下标做value。

直观方法是两次遍历,第一次遍历缓存,第二次遍历查找。

思路不难,直接上代码,一个需要注意的点是同一个下标不能使用两次。(但是题目里其实没有说数组中的元素都不相等,其实这里是有可能出现冲突的,那么HashMap里保存的应该是List才对,但实际上这个代码也能ac,就懒得写了,而且下一个优化方法能解决这个问题)

import java.util.HashMap;

class Solution {
    public int[] twoSum(int[] nums, int target) {
        HashMap map = new HashMap();

        for (int i = 0; i < nums.length; i++) {
            map.put(nums[i], i);
        }

        for (int i = 0; i < nums.length; i++) {
            int toFind = target - nums[i];
            if (map.containsKey(toFind) && map.get(toFind) != i) {
                return new int[] {i, map.get(toFind)};
            }
        }
        return null;
    }
}

解法三:HashMap缓存,一次遍历

这个方法确实没想到,看了官方解法才知道还能做进一步优化。厉害!

其实思路很简单。在一次遍历过程中,其实就能边遍历边使用已有的历史信息了。只要当前正在访问的这个元素能和以前已经遍历到的某个元素组成target,那么就可以直接返回了。而且这样一次遍历完成之后,一定是能确保找到答案的(这个可能要证明?没想到简单的证明方法,根据推理是可以知道的,不展开了)

代码如下,这个还不需要考虑用到的索引是相同的,因为当前访问的元素是在进行target判断之后才放入HashMap中的,不会重复。

class Solution {
    public int[] twoSum(int[] nums, int target) {
        HashMap map = new HashMap();

        for (int i = 0; i < nums.length; i++) {
            int toFind = target - nums[i];
            if (map.containsKey(toFind)) {
                return new int[] {i, map.get(toFind)};
            }
            
            // 在target查找之后再放入map,避免重复使用本元素。
            // 那么,如果可以重复使用的场景,是不是可以把它挪到前面实现重复使用呢?
            map.put(nums[i], i);
        }
        return null;
    }
}

 

耗时:

从开始做题到写完文章,消耗了两个番茄钟(每个25分钟)。看来这个事情还是挺耗时的,这还是简单的题目,就已经花费了这么长的时间,做个记录,有个心理预期和时间估算。

 

 

 

你可能感兴趣的:(算法)