天天看点

LeetCode-#1-两数之和(Two Sum)

题目:

给定一个整数数组和一个目标值,找出数组中“和”为目标值的两个数。

你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。

示例:

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

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

看到这个问题的时候,优先想到了一开始学习Java时老师教的方法,没错,就是暴力解决法,使用循环嵌套,判断筛选出符合条件的值,如下:

class Solution {
    public int[] twoSum(int[] nums, int target) {
        for (int i = 0; i < nums.length; i++) {
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[j] == target - nums[i]) {
                    return new int[] { i, j };
                }
            }
        }
        throw new IllegalArgumentException("没有想要的结果");
    }
}
           

比较好理解,拿出数组中的某个数,和其他的数求和,看看是不是和目标值相同。(为了避免重复求和,在子循环中,初始值用的是父循环的下一个值,因为并不是所有值都需要从第一个开始求和判断,比如说第三个值,它只需要和第四个以及第四个之后的求和判断就可以了,因为它之前的值,早在第一个、第二个值循环求和判断的时候和它相加过了。并且题目要求元素不能重复利用,所以这里用到“i+1”。)

对是对了,但提交代码后我懵圈了。。。运行时间:56ms,超过13.11%的人。纳尼?还有这个统计。。我岂能忍得了这个排名!

开始改良代码,首先我觉得主要是我用到了两个循环的问题,那么我就舍弃一个循环。可是怎么做呢?我想到一个方法,首先我将nums数组转换成一个List,然后在一个循环中,看目标值和nums里值的差是否存在于List里,如果存在,那就找到了这两个值了。

但是问题来了,基本类型的数组没法直接转换成List,这就有点尴尬了,不过我又有了一个点子。

那就是逆向思维,我先new一个List,然后在循环求差判断的时候,如果符合条件,我就return结果,如果不符合条件,我就将当前的nums里值放到List里,然后下次循环,会将求差结果和List里的值比较,之前的方法是不用和当前值之前的值进行比较,现在是每个值不用和它之后的值进行比较,因为后面的值会和它之前的值(之前add到List里的所有的值)比较,同样起到了避免重复判断的作用。

这里我说的太绕,换句话说:在进行循环并将元素插入到List中的同时,我们还会回过头来检查List中是否已经存在当前元素所对应的目标元素(求差结果)。如果它存在,那我们已经找到了对应解,并立即将其返回。代码如下:

class Solution {
    public int[] twoSum(int[] nums, int target) {
        List<Integer> numsList = new ArrayList<Integer>();
        for(int i=0; i<nums.length; i++){
            int j = numsList.indexOf(target - nums[i]);
            if(j>-1 && j!=i){
                return new int[]{j,i};
            }
            numsList.add(nums[i]);
        }
        throw new IllegalArgumentException("没有想要的结果");
    }
}
           
LeetCode-#1-两数之和(Two Sum)

啊?啊?啊♂~~~~为啥这么慢。。ArrayList查询比较速度应该很快啊。。。

额,忽略一件事,因为ArrayList底层是数组实现的,增删操作会带来元素的移动,增加数据会向后移动,所以极大影响效率。

既然这样,那就用同样的思想,换成HashMap好了,哈希表添加值的话是很快的,将nums里值作为HashMap的key,将下标作为value,这样就可以用containsKey方法找到符合条件的键值,和List的indexOf在这里有异曲同工之处。

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer,Integer> map = new HashMap<Integer,Integer>();
        for(int i=0; i<nums.length; i++){
            int key = target - nums[i];
            if(map.containsKey(key)&&map.get(key)!=i){
                return new int[]{map.get(key),i};
            }
            map.put(nums[i],i);
        }
        return new int[]{};//这样比异常更快,为了速度。。
    }
}
           
LeetCode-#1-两数之和(Two Sum)

果不其然,这个结果就让我很满意了。

很好奇大佬们究竟用了什么方法,能够更快,于是参考了大佬的代码。我哩个龟龟。。。二进制运算。。。在这里贴一下Top1的代码,用时 3ms,供大家观赏。

class Solution {
 
   public int[] twoSum(int[] nums, int target) {
      final int il = nums.length;
        int il2 = (il >> 2) - 1;

        int pot = 2;
        while((il2 >>= 1) > 0) pot <<= 1;
        final int bitMod = pot - 1;
        final int[] bucket = new int[pot];
        final int[] linked = new int[il];

        final int firstVal = nums[0];

        for (int i = 1; i < il; i++) {
            int currNum = nums[i];
            int complement = target - currNum;

            if (complement == firstVal) {
                return new int[] { 0, i };
            }

            int complementLLIndex = bucket[complement & bitMod];
            while(complementLLIndex != 0) {
                if(nums[complementLLIndex] == complement) {
                    //Found
                    return new int[] { complementLLIndex, i };
                }
                complementLLIndex = linked[complementLLIndex];
            }
            int currNumLLIndex = currNum & bitMod;
            linked[i] = bucket[currNumLLIndex];
            bucket[currNumLLIndex] = i;
        }
        return null;          
    }
}