代码随想录—哈希表

98 阅读6分钟

哈希表

基础概念

哈希表是根据关键码的值而直接进行访问操作的数据结构,一般用于快速判断元素是否在集合中。

哈希表通过哈希函数把传入的key映射到符号表的索引上(类似于在字母异位词中以数组作为哈希表时,对每个字母下标的处理是 s[i] - 'a'

哈希表通过哈希碰撞的两种方法来处理多个key映射到相同索引的情况,有拉链法(key值后接链表存储所有value)和线性探测法(找下一个空位填充)

基本哈希结构

数组 下标就是key,具体值就是value

set(集合) set分别提供以下三种数据结构

集合底层实现是否有序数值是否可以重复能否更改数值查询效率增删效率
set红黑树有序O(log n)O(log n)
multiset红黑树有序O(logn)O(logn)
unordered_set哈希表无序O(1)O(1)

需要用到集合时优先使用unordered_set,需要集合是有序的就用set,要求不仅有序还要有重复数据就用multiset

map(映射) map分别提供以下三种数据结构

映射底层实现是否有序数值是否可以重复能否更改数值查询效率增删效率
map红黑树key有序key不可重复key不可修改O(logn)O(logn)
multimap红黑树key有序key可重复key不可修改O(log n)O(log n)
unordered_map哈希表key无序key不可重复key不可修改O(1)O(1)

在映射的三种数据结构中是对key值做限制,map和multimap底层是红黑树实现,所以key值有序,即传入的数据会根据key值排序

1. 字母异位词 数组(大小受限的简单哈希表)

思路: 通过一个26位的数组存储每个字母在字符串s中出现的次数,再遍历字符串t,把t中出现过的字母的次数减去,最后判断数组中是否存在不为0的值,若有,则说明某个字母没有在两个字符串之间对应出现

重点在于字母到数组下标的映射操作:s[i]-'a'即字符串字母到数组位置的映射,因为字母的ASCII码是连续的,所以减 'a' 就相当于以 'a' 的位置为数组初始位置 0

例题:242. 有效的字母异位词 image.png

2.两个数组的交集 set(此题中大小不定)

思路: 利用哈希表1存储数组1中的元素,再依次在哈希表1中查询数组2的各个元素,若查询到则将结果插入到哈希表2中,使用unordered_set,能对插入的数据自动去重

例题:349. 两个数组的交集 image.png

3.快乐数 set存放自动去重 循环处理个位数

思路: 这个问题最难的点我认为在于怎么想到进入循环之后出来的方式:

1.无限循环->有重复数字

2.最后处理出来等于1->输出快乐数

知道了要找重复数字就想到用unordered_set来存放,能够自动去重,每一轮的结果都到set里查询,查询到就说明进入循环,返回false,没查找到就把这个数先存入set,继续下一轮,直到查找到或是对数据处理之后等于1

当然对数据的处理也比较巧妙,额外用一个getSum函数来处理每一轮的数,用while循环来对数的个位进行处理(n%10,n模10得到的就是个位数),然后每轮循环都让 n/=10,循环条件就是 n,这样 n 有几位就会循环几轮,且每轮在sum+=后都会舍弃个位

例题:202. 快乐数 image.png

4.两数之和 map存放key和value a=target-b

思路: 因为既要有下标又要判断值,所以用unordered_map来存储,key值存放数值(要用于比较),value存放下标,又因为a+b=target,则a=target-b,用一层循环向后遍历找b,前面找到不符合条件的b就存放在map中,这样用一个map存放遍历过的元素,一层循环就能实现两个数的判断

例题:1. 两数之和 image.png

5.四数相加Ⅱ 分组查询

思路: 总体思路是把四个数组分为A+B,C+D两组,用unorder_map存储A+B对应的值(key)和出现次数(value),然后查询C+D值的相反数是否在A+B中出现过,有则累加出现次数

例题:454. 四数相加 II image.png

6.赎金信 数组存放小写字母(ASCII码连续) 下标索引映射

例题:383. 赎金信

思路1: 总体思路同有效字母异位词,通过一个26位的数组存储magazine中字母出现的次数,再减去ransomNote中字母出现的次数,若小于0则返回false image.png

思路2: 暴力法 外层循环要是magazine,因为是判断ransomNote能不能由magazine里面的字符构成,且字母不重复使用,所以ransomNot中找到一个就要删除一个,所以ransomNot在里层,每删一次就break出去检查magazine的下一个字符 image.png

7.三数之和 去重 剪枝 nums[i]+nums[left]+nums[right]=0

思路: 双指针,要使a+b+c=0,重点在于对a、b、c各自的去重和剪枝操作

  1. 对a:

(剪枝) if(nums[i] > 0) break; 因为是排序过后的数组,所以a<b<c,若a都大于0了那么a+b+c不可能等于0,可以舍去

(去重) if(i > 0 && nums[i] == nums[i - 1]) continue; 这里是比较 i 和 i-1 而不是 i+1,因为 i+1 的话说明当前指向的还是第一个位置,第二个位置的值与之相同,还没有开始往后查询就直接跳过了,有可能会有遗漏,而 i-1 说明第一个位置已经做过后续的操作了,那么第二个位置才能跳过

  1. 对b:(去重)while(right > left && nums[right] == nums[right - 1]) right--;

  2. 对c:(去重)while(right > left && nums[left] == nums[left + 1]) left++;

需注意对b、c的去重至少要在找到一组满足条件的值之后,若是在刚进入移动左右指针的循环时去重可能会直接跳过该值

例题:15. 三数之和 image.png

8.四数之和 去重 剪枝 nums[i]+nums[j]+nums[left]+nums[right]=target

思路: 在三数之和外层再嵌套一层循环找第四个数,同时要注意对每个数(每个循环)的去重和剪枝操作

例题:18. 四数之和 image.png