哈希表:初步理解 & Leetcode 相关题目:242.有效的字母异位词 349. 两个数组的交集 202. 快乐数 1. 两数之和 454.四数相加II

初步理解

『教程』哈希表是个啥?_哔哩哔哩_bilibili

参考教程如上,除此之外参考视频下方的首条评论也讲的很好。

哈希的基本概念

假设我们有几万条商品价格的数据存储在内存中,想要快速找到“苹果”的价格。如果我们不知道这些商品的存储规律,就只能一个个去查找,这会非常耗时。此时的时间复杂度为遍历情况下的O(n)

但如果我们记得每个商品价格存储在哪个位置,就可以直接查找相应位置的商品价格,此时时间复杂度仅为O(1)。哈希函数正是帮助我们快速定位数据的一种方法。

此时的重点则在于我们要如何直接知道地址呢,此时要利用的就是哈希函数


哈希函数

可以当做一种转换器,内部置有一定的规则,键值对这个概念用在此处就能很好解释这种函数的思想。

当我输入一个特征(键)时,哈希函数会根据内部规定的规则生成一个对应的整数值(哈希值)。我们可以把这种形式看作一个保险箱系统,其中特征是用来打开箱子的钥匙,而整数值对应的地址就是储存信息的箱子。

如果没有这个计算函数,我们就需要用钥匙一个一个试,每个箱子的锁孔都试一下,看看能否打开箱子。而有了哈希函数,相当于在检索的时候,作为一个根据关键词匹配内容的系统,能够直接给出特定的地址,直接匹配到对应的保险箱。


哈希表的使用场景&对应容器

当我们遇到了要快速判断一个元素是否出现在集合里&判断一个元素是否出现过的场景的时候,就要考虑哈希法

我们要根据题目的要求,因地制宜的选择不同的数据容器,进而降低时间复杂度,提高运算速度。


一.数组:索引作键,索引对应位置元素作“值”,构成键值对

适用条件:哈希值密集、范围较小、数据量少且稳定的场景。

(1)数组的大小固定,扩展性较差。当数据量超过预设大小时,需要重新分配更大的数组,并将现有数据复制过去,这会带来额外的时间和空间开销。

(2)如果哈希函数生成的哈希值比较少且非常分散,跨度非常大,比如总可能情况为10w种,哈希值从0到1,000,000之间,但实际数据只有100个。那么数组的大小需要涵盖从0到1,000,000的范围,即使只有100个元素,数组也需要分配1,000,001个位置。

在下方的学习中,我们能逐渐体会到上述两点。

哈希值大部分情况下不是我们自己定的,而是题目给出的要求,我们只是按要求进行统计或判定。


242.有效的字母异位词 

题目链接:242. 有效的字母异位词 - 力扣(LeetCode)

参考教程:学透哈希表,数组使用有技巧!Leetcode:242.有效的字母异位词_哔哩哔哩_bilibili


思路流程:

此题用到的字母在编码中处于连续状态,而数组正好也要求数组元素连续分布,因此考虑选择数组作为此处的哈希表容器。

bool isAnagram(string s, string t) {

	int harsh[26];

	for (int i = 0; i < s.size(); i++) {
		harsh[s[i] - 'a']++;
	}

	for (int i = 0; i < t.size(); i++) {
		harsh[t[i] - 'a']--;
	}
	
	for (int i = 0; i < 26; i++) {
		if (harsh[i] != 0)return false;
	}
	return true;
}

可以看到,代码以索引位置为键,对一开始初始化为0的各位置数值进行加减运算。因为26个字母在编码时即为连续分布,因此可通过类似s[i] - 'a'的形式来确定位置,并进一步完成频率统计。

349. 两个数组的交集(适用于数值较小情况)

重要知识点:

(1)具备条件1 <= nums1.length, nums2.length <= 1000,数组的总情况数较少,最多只有1000种,预留一定空闲空间,1005即可得到一个合理大小的数组结构。此处能用数组作为1哈希结构,完全是因为上述限制条件,规定了数据集的大小较小。

(2)数组作为哈希结构的去重

 for (int num : nums1) { // nums1中出现的字母在hash数组中做记录
            hash[num] = 1;
        }

即只用数值0,1来进行判定,只要出现,无论多少次数值都为1,不出现则为0。某种意义上是布尔运算


二.集和(set):哈希值用于快速查找,元素本身存储在集合中

个人理解:集合中的哈希函数是find()函数

程序员仅需将特征名放在括号中,eg:xx.find("apple"),find函数便能给出哈希值,找到集和中的对应元素。


1.适用条件:需要快速去重、查找,且数据量较大、动态变化的场景。

(1)去重功能天然支持去重操作,因为每个元素在Set中都是唯一的。这对于需要处理大量唯一元素的场景非常有用。

 (2)空间利用率高:集和在存储上更高效,因为只存储实际存在的元素,避免了大量空闲位置的浪费,并且在哈希值跨度大、数据稀疏时也能有效管理内存,提升空间利用率。


349. 两个数组的交集(对数组范围不做要求)

题目链接:349. 两个数组的交集 - 力扣(LeetCode)

参考教程:学透哈希表,set使用有技巧!Leetcode:349. 两个数组的交集_哔哩哔哩_bilibili

当对范围和哈希值大小均不作限制后,数组明显不是一个好选择。因此选择使用集和结构——>适用于数据量大的复杂环境。

unordered_set<int> result_set;
       unordered_set<int> num_1(nums1.begin(),nums1.end());
       for(int i = 0;i<nums2.size();i++){
        if(num_1.find(nums2[i]) != num_1.end()){
            result_set.insert(nums2[i]);
        }
       }


 三.映射(map):使用键值对

个人理解,与数组有点类似,是最符合哈希表概念的结构形式,可以自己定义键名和对应数值。


1. 两数之和

题目链接:1. 两数之和 - 力扣(LeetCode)

参考教程:梦开始的地方,Leetcode:1.两数之和,学透哈希表,map使用有技巧!_哔哩哔哩_bilibili


个人理解:

通过映射把单个数组展开成两份——数据集与“验证集”(空间换时间)

哈希法适用于验证一个元素是否存在于集和中的情况,单个数组显然没法自己验证自己,此时就由映射结构来额外开辟一个验证集。

每检测一次数据集中的元素,就去验证集中寻找是否有相配的元素,若没有,则将当前检测的元素放入验证集中,检测下一个元素。如此,虽然空间的占用量变为了原来的两倍,但是效率却由暴力的两层for循环的O(n^2)提高到了O(n)——》每次只有一个元素,检索一遍“验证集”中是否有相配元素即可。

具体代码实现如下:

vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> used_num;

    for (int i = 0; i < nums.size(); i++) {
	  int current_num = nums[i];
	  int need_num = target - current_num;
	  if (used_num.find(need_num) != used_num.end()) {
		
		return {used_num[need_num],i};
	  }
	  else {
		used_num.insert(pair<int, int>(current_num, i));
	   }
    }
    return{};
  }

也是由这题,我才开始明白哈希表中一直提到的空间换时间的含义究竟是什么


454.四数相加II
这道题更是一道哈希表的经典题目,强烈建议观看下方视频,老师讲的很好


关键要点:
转化问题: 把四数相加,转换为两个两数相加的形式。这样AB数组的元素之和可以作为数据集元素,CD数组的元素之和可以作为验证集元素。只要看数据集中单个元素的相反数是否出现在验证集中即可——》由此, 情境变为了适合通过哈希表解决的问题形式。
效率提升:暴力检索O(n^4) ——》O(n^2)
unordered_map<int, int> test;

     for(int i = 0;i<nums1.size();i++){
	    for (int q = 0; q < nums2.size(); q++) {
		 int plus = nums1[i] + nums2[q];

		 //要检测元素的出现情况,那么肯定是拿两者的和来作为key

		 test[plus]++;
	    }

       } 

     int count = 0;

     for (int i = 0; i < nums3.size(); i++) {
	  for (int q = 0; q < nums4.size(); q++) {
		int plus = 0 - nums3[i] - nums4[q];

		if (test.find(plus) != test.end()) {

			count += test[plus];
		}
	    }

    }

       return count;

    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值