【LeetCode】1.两数之和

19 篇文章 0 订阅

0.题目分析

这道题目要求给定一个整数数组 nums 和一个目标值 target,在该数组中找出和为目标值的 两个 整数。

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

比如:

看到题目,首先想到的是暴力搜索法。

1.暴力解法

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
    int len = nums.size();
    vector<int>twoInt(2);
    for (int i = 0; i < len; i++)
    for (int j = i+1; j < len; j++)
	{
		if (nums[i] + nums[j] == target)
           {
               twoInt[0]=i;
               twoInt[1]=j;
           }
	}
        return twoInt;
    }
};

此种解法有两层循环,循环里的操作有一个判断和一个赋值,都是常数复杂度,所以整个解法复杂度是O(n^2),比较复杂。查看结果也可以看到速度慢:

2.哈希表

2.1.哈希表的原理

     哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。            

    给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。

        总结起来就是:记录的存储位置=f(关键字)

       哈希表就是把key通过哈希函数转换成一个整型的数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下

标,将value存储在以该数字为下标的数组空间里,通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同输出。

        什么意思呢,简单来说,举个例子,假如数组的下标是经过取余过的,比如一个数组的长度为10,哈希表把key1通过哈希函数转换为一个整型数字15,15对10取余为5,5当作数组的下标,而假如哈希表把key2通过哈希函数转换为一个整型数字25,25对10取余也为5,很明显这里key1≠key2,但f(key1)=f(key2),这就是哈希冲突。假如把上面说的数组长度设为1,可以避免哈希冲突,但是空间会增大。所以哈希冲突实际无法避免,但可以减少哈希冲突。

        使用哈希表查询的时候,就是再次使用哈希函数将Key转换为对应的数组下标,并定位到该空间获取value,如此一来,就可以充分利用到数组的定位性能进行数据定位。

2.2哈希表的实现

    哈希表有不同的实现方法,下面讲一下拉链法,可以理解为“链表的数组”。

        左边是一个数组,数组的标号就是由上面我们提到过的f(key)算出来的,也就是记录的存储位置。数组的每个成员包括一个指针,指向一个链表的头,当然这个链表可能为空,也可能元素很多。我们根据元素的一些特征把元素分配到不同的链表中去,也根据这些特征,找到正确的链表,再从链表中找出这个元素。

2.3哈希表的应用

        有了以上的基础知识后,很显然我们可以用哈希表进行查找,我上一种解法是所谓的“暴力解法”:遍历集合中的所有元素,看是否是需要的,需要的话就拿出来,不需要就继续查找。而哈希表是完全另外一种思路:当我知道Key值后,我就可以直接计算出这个元素在集合中的位置,根本不需要一次次地查找。

        所以对于LeetCode利用哈希表的思路重写两数之和的代码,主要思路就是找到一种数据内容和数据存放地址之间的映射关系:

                                                               

      如图所示,我们把nums[]数组里的值,当成哈希表的keys值,比如nums=[2,7,11,15] ,那么hash[2]=0,hash[11]=2。

       另外需要说明的是:

       (1)C++里map映射类型可以用count函数的意思是keys值出现的次数,可以来辅助判断是否里面有此keys。比如map<int, int> hash; hash.count(2)=1,hash.count(7)=1,但hash.count(8)=0。

      (2)hash函数映射的时候,若映射没有出现过的keys值,那么映射的结果为0。比如hash[3]=0。

基于上面两个性质,我们此题可以分别写两种判断语句:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
	int len = nums.size();
	vector<int> twoInt;
	map<int, int> hash;
	for (int i = 0; i < len; i++)
		hash[nums[i]] = i;
	for (int i = 0; i < len; i++)
	{
		int a = target - nums[i];
		if (hash[a]&&hash[a]!=i) //或者(if(hash.count(a)&&hash[a]!=i)
		{
			twoInt.push_back(i);
			twoInt.push_back(hash[a]);
			break;
		}
	
	}
        return twoInt;
	
    }
};

此种解法有两个循环,每个循环时间复杂度都为O(n),整体时间复杂度为O(n)。此种解法的思想在于,还是举上面的例子,即有数组nums可以通过0,1,2,3找到对应的数字2,7,11,15,又有Hash可以通过2,7,11,15可以找到0,1,2,3,所以在遍历的时候可以得到    a=target-nums[i]的值,假如在2,7,11,15中,即可通过hash表把a对应的数找出来。

需要说明的是这种解法存哈希表的时候是从前往后,后面的覆盖前面的,判断的时候也是从前往后,由前面的数找到对应后面的数为key值的数 所以不会哈希冲突。

对应用时:

2.4一遍哈希表

上面的解法我们用了两遍遍历,一遍是给哈希表赋值,另一遍是寻找哪两数之和是target,那么我们能否把这两个过程统一在一个遍历里呢?答案是可以的,但此时的判断语句不能写hash[a],因为两遍hash表的时候,寻找两数的时候从i的0开始找后面的数,不会找第0个数,但是这种办法可能找到第0个数,比如nums={3,3},此时第一次hash映射的时候hash[3]=0,若用此种判断语句,则冲突,代码如下:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
	int len = nums.size();
	vector<int> twoInt;
	map<int, int> hash;
	for (int i = 0; i < len; i++)
	{
		int a = target - nums[i];
		if (hash.count(a) && hash[a] != i){
			twoInt.push_back(i);
			twoInt.push_back(hash[a]);
		}
        hash[nums[i]] = i;

	}
        return twoInt;
	
    }
};

时间复杂度也为O(n),速度差别不太大。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值