LeetCode-探索-初级-数组-两数之和-java

两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的 两个 整数。

你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

示例:

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

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

方法一:

不需要排序,直接通过两个下标在整个数组中顺序的寻找两个值即可,值得注意的是,第二个下标总是大于第一个下标

如果没有注意到这一点,代码不仅效率低下,也很有可能出现同一元素被重复利用的错误

public int[] twoSum(int[] nums, int target) {
        int[] result = new int[2];
        for (int i = 0 ; i < nums.length ; i ++)
            for (int j = i + 1 ; j < nums.length ; j ++)
                if (nums[i] + nums[j] == target) {
                    result[0] = i;
                    result[1] = j;
                    return result;
                }
        return result;
    }

最佳方法(官网):

emmm,,,之前没有见过这种处理方式,算是开眼了,但是由于题目的严谨性,其实官网上面的最佳解法并不是没有破解的方法。利用一些极为刁钻的用例还是可以防止通过。

以下是官网的解法:

public int[] twoSum(int[] nums, int target) {
    int[] indexs = new int[2048];  //别问我我也不知道为什么是这么大的一个数组
    int bitMode = 2047;  //等会就明白这个值是干什么的了
    for (int i = 1; i < nums.length; i++) {  //遍历
        int subNum = target - nums[i];  //求差值
        if (subNum == nums[0]) {  //判断是否是和第一个数相同,防止和后面的idnex = 0冲突
            return new int[]{0, i};  //返回
        }
        //---这之后的代码就有点意思
        int index = indexs[subNum&bitMode];  //精髓之处,将下标为差值的indexs中的值取出
        if(index != 0) {  //经过之前的检查,如果index不为0,证明之前已经有nums[i]注册过
            return new int[]{index, i};  //返回注册的下标
        }
        indexs[nums[i]&bitMode] = i;  //如果没有找到,注册当前的下标
    }
    throw  new IllegalArgumentException("not exist!");  //如果遍历数组都没有找到,抛出异常
}

好,可能看到这里有些人会有疑问,为什么在之后的代码中,出现了如下的代码:

int index = idnexs[subNum & bitMode];

indexs[nums[i] & bitMode] = i;

为什么要进行与运算?

其实就是为了防止nums中出现负值的情况,但是这样做会出现一些意想不到的结果。

大家都知道,在计算机中,数据是用补码的形式存储的。好,现在我们来想一想“-1”这个int吧

补码可以利用对位取反 + 1来求,也可以利用公式2^{n+1}+x来求(其中的x是真值,即带符号的数字)
1         的二进制表示 00000000 00000000 00000000 00000001

2^{n+1}(2^{32})的二进制表示  1 00000000 00000000 00000000 00000000

相减之后得到了:11111111 11111111 11111111 11111111 ,也就是int的最小值

现在那这个最小值和2047进行与运算,你觉得会发生什么?

答案是,还是2047;

好,既然我们知道了 -1 经过与运算之后是 2047

那么,,,是不是只要是 XXXXXXXX XXXXXXXX XXXXX111 11111111 这样的二进制数和2047经过与运算之后都是2047呢?

肯定是啊~不然我就不接着往下写了;

举个最简单的例子,-1 和 2047; -2 和 2046 ...

也就是说,若a > 0

只要满足 a - b = 2047 【在12位二进制之内考虑】

a 和 b一定能映射在indexs数组的一个下标上~

现在是标准答案的反例时间~【我直接把自己的测试代码放在上面吧~】

package LeetCode;

public class TwoSum {
    public static void main(String[] args) {
        int[] test = new int[]{5, -1 ,2047, 0, 100};
        TwoSum twoSum = new TwoSum();
        twoSum.printArray(twoSum.twoSum1(test, 99));
    }

    /**
     * 将 i 的值记录在 indexes[nums[i]] 中
     * 若将来找到一个 target - nums[i] == indexes[k];
     * 则 k 一定是之前登记过下标的,即,若k登记过下标,则结果为 k, i
     * 但是当nums[i]为负数呢?
     * 可能负数和正数会映射成为一个值
     * @param nums
     * @param target
     * @return
     */
    public int[] twoSum1(int[] nums, int target) {
        int[] indexs = new int[2048];  //别问我我也不知道为什么是这么大的一个数组
        int bitMode = 2047;  //等会就明白这个值是干什么的了
        for (int i = 1; i < nums.length; i++) {  //遍历
            int subNum = target - nums[i];  //求差值
            if (subNum == nums[0]) {  //判断是否是和第一个数相同,防止和后面的idnex = 0冲突
                return new int[]{0, i};  //返回
            }
            //---这之后的代码就有点意思
            int index = indexs[subNum&bitMode];  //精髓之处,将下标为差值的indexs中的值取出
            if(index != 0) {  //经过之前的检查,如果index不为0,证明之前已经有nums[i]注册过
                return new int[]{index, i};  //返回注册的下标
            }
            indexs[nums[i]&bitMode] = i;  //如果没有找到,注册当前的下标
        }
        throw  new IllegalArgumentException("not exist!");  //如果遍历数组都没有找到,抛出异常
    }

    public int[] twoSum(int[] nums, int target) {
        int[] result = new int[2];
        for (int i = 0 ; i < nums.length ; i ++)
            for (int j = i + 1 ; j < nums.length ; j ++)
                if (nums[i] + nums[j] == target) {
                    result[0] = i;
                    result[1] = j;
                    return result;
                }
        return result;
    }


    public void printArray(int[] nums) {
        for (int i = 0 ; i < nums.length ; i ++)
            System.out.print(nums[i] + " ");
    }
}

执行的结果是:2 4

但是target是99~


第三种方法:

借鉴这种思路,我们可以有第三种方法:

public int[] twoSum3(int[] nums, int target) {
        int[] result = new int[2];
        Map<Integer, Integer> indexs = new HashMap<>();
        int subTarget = 0;
        Integer result0 = null;
        for (int i = 0 ; i < nums.length; i ++) {
            subTarget = target - nums[i];
            result0 = indexs.get(subTarget);
            if (result0 != null) {
                result[0] = result0;
                result[1] = i;
                return result;
            } else
                indexs.put(nums[i], i);
        }
        return result;
    }

官网上运行时间次一级的基本上也是这种情况~


其实,官网上的最佳解法也并不是不能使用,只不过最后加上一步是否真的是我们期望的

猜你喜欢

转载自blog.csdn.net/jdie00/article/details/84583990
今日推荐