两数之和
给定一个整数数组 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来求,也可以利用公式+x来求(其中的x是真值,即带符号的数字)
1 的二进制表示 00000000 00000000 00000000 00000001
()的二进制表示 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;
}
官网上运行时间次一级的基本上也是这种情况~
其实,官网上的最佳解法也并不是不能使用,只不过最后加上一步是否真的是我们期望的