Two sum
题目要求:
Given an array of integers, return indices of the two numbers such that they add up to a specific target. You may assume that each input would have exactly one solution, and you may not use the same element twice.
就是说一个数组都是整数 我们需要得到两个标记位置的number,比如这个数组的第一个位置的数和第二个位置的数,然后让他们的和等于一个确切的数,每个数不能使用两次。这个确切的数只能被一组数加出来。
Example: Given nums = [2, 7, 11, 15], target = 9,Because nums[0] + nums[1] = 2 + 7 = 9, return [0,1].
首先我个人的思路是,我们先取出第一个数,然后逐行遍历,挨个求和,看看有没有和第一个数相加等于target的。
##方法一
class Solution {
public int[] twoSum(int[] nums, int target) {
int ans[]=new int[2]; //先创建一个生成答案的数组
for(int i=0;i<nums.length;i++) //从数组第0位开始遍历
{
for(int j=1;j<nums.length;j++)//从数组第一位开始遍历
{
if(nums[i]+nums[j]==target){ //这两个数相加等于目标值
ans[0]=i; //把求得的i放入答案数组第一位
ans[1]=j; //把求得的j放入第二位
return ans;//输出答案
}
}
}
return null; //如果遍历整个数组都没有答案 输出null
}
}
这样的话时间复杂度很明显算出 O(N^2) 因为大体上是近乎是需要遍历两次的。
我没有想出可以减少时间复杂度的方式,所以看了答案,答案提供了Hash表的方法(没学过)
Hash表:
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
这是百度的解释,完全没看懂,去了知乎,找到一篇大佬【invalid s】解释,太好懂了,转载如下:
-
想象一下:有一天,你开车到某商场去买东西。买完东西,你想不起自己的车在哪了。这家商场非常非常大,楼下停车场少说停了上万台车;而你的健忘症又比较厉害……那么,问题来了:你怎样才能找到自己的车?一个理想的情形是,你从头到尾一行一行一辆一辆按顺序走一遍就找到了;运气最差时,你搜遍楼下N辆车,发现你的车在末尾——拿术语说,这个复杂度是O(N)。
-
能不能更快一些呢?很简单,假如所有车按车牌号顺序排列,你直接往停车场中间走就行了;如果你的车牌号大于中间那辆车,那么你就往停车场后半部分的中间走,否则就往前半场走……依此类推。如此一来,你最多只需要走ln(N)次“中间”,就能找到你的车了。黑话叫复杂度O(ln N)。还能不能更给力一些?
-
可以。假设这个停车场非常非常大,大到可以给每个车牌号分配一个固定的停车位;那么只要你把自己的车牌号报给看门老头,他拎着你的衣领子往后一丢,你就“吧唧”一下掉自己车顶上了——嗯,你看,一车一位,就是这么任性。这就叫“查找复杂度O(1)”。你的车牌号就是数组下标,所以你只需直接访问park[CAR_NUMBER]即可
那么,第三个设计是不是完全解决问题了呢?并不是。很容易看出,第三个方案需要一个超级大的存储空间。这个空间得有多大呢?它必须大到足以和过去未来的一切有效车牌号一一对应,你才可能做到“直接按号访问”。假设车牌号共8位,每位可以使用26个英文字母或10个阿拉伯数字,那么不同的车牌号共有36^8=2821109907456种。哪怕每辆车只需一个字节的存储空间,这也是接近3T的空间!而事实上,哪怕最大的超市,修一个够停一万辆车的停车场也都太夸张了。你看,这完全行不通啊。
哈希表:
我们把你的车牌号除以一万,只取余数——你看,你的车牌号是不是就和0~10000之间的数字对应起来了? 2821109907456/10000 余7456
很好,你的车就停在这个数字对应的停车位上,过去开就是了——O(1)的查找效率!这个“把你的车牌号映射进0~10000之间”的操作,就是所谓的“散列”“杂凑”“哈希”或者hash(当然,实践上,为了尽量减少冲突,哈希表的空间大小会尽量取质数)。相对于“以key为下标直接访问的数组”,哈希表是“时间换空间”;相对于二分法查找,哈希表又是“以空间换时间”。这种“中庸”的定位使得它在许多场合极为好用。
等等,你发觉不对:我的车尾号456,我朋友的车也是这个尾号。我们总不能停在同一个位置吧?你这个方案有瑕疵啊!没错,hash可能会把不同的数据映射到同一个点上,术语称其为“碰撞”。由于hash自身的基本原理,碰撞是不可避免的。怎么解决这个“碰撞”问题呢?几种解决思路:
1、临时加个“立体车库”,哪里碰撞往哪放。于是车子就可以在同一位置“撂起来”存了。这叫“开链表法”。
2、车库面积肯定是够的。456号被人占了,你存457不就好了!换句话说,过去的散列函数是 (车牌号 模除 10000),发现碰撞了就换散列函数 (车牌号加1 模除 10000)试一试——这叫“再散列法”。
3、再修个小车库,碰撞了的停小车库去(小车库可以随便停,也可以搞一套别的机制)总之,如此一来,我们就同时得到了“O(1)的查找效率”和“可接受的空间消耗”。任何时候,当你有“数量有限”但“不同索引数量极大”的一些数据,必需极高的访问效率同时又不想无端消耗太多的存储空间时,你就可以考虑使用哈希表了。当然,请注意,因为冲突的存在,哈希表虽然有着优异的平均访问时间(常数访问效率!);但它的“最大访问时间”却是没有保证的——你可能一个微秒甚至几个纳秒就拿到了数据,也可能几十个毫秒了还在链表上狂奔。因此实时性要求严格的场合,用它前需要谨慎考虑。
##方法二
哈希表用于本题,我们把这个数组里面的数转化为一个哈希表,数是key,数在数组中的位置是哈希表中的value。那当我们已经有了一个数的时候,我们其实已经知道了我们想要的另一个数是什么,没错,就是target- nums【i】。 那使用哈希表,我们就可以像停车场老头揪住我们领子把我们直接扔到车顶一样,直接得到想要的value(想要的车)【因为哈希表自己已经知道这个key的值对应的value是什么了】。这样看我们是不是只用做一次循环,拿出一次数就可以了,时间复杂服降为O(n)了。⚠️ 题中要求不能两次使用同一个数,所以我们要做判断
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> 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 a=target-nums[i]; //求出我们想要的数的值,也就是哈希表的key
if(map.containsKey(a)&&map.get(a) != i) //如果表中的key值包含我们想要的a,而且a值所在的value不是i
return new int[] {i,map.get(a)}; //我们得到我们想要求的答案,放在一个新数组中
}
return null;
}
}