Task01 哈希表(1-2天)
1.1哈希表简介
哈希表(Hash Table):也叫做散列表。是根据关键码值(Key Value)直接进行访问的数据结构。
哈希表通过「键 key 」和「映射函数 Hash(key) 」计算出对应的「值 value」,把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做「哈希函数(散列函数)」,存放记录的数组叫做「哈希表(散列表)」。
1.2哈希函数
哈希函数(Hash Function):将哈希表中元素的关键键值映射为元素存储位置的函数。
关于整数类型的关键字,通常用到的哈希函数方法有:直接定址法、除留余数法、平方取中法、基数转换法、数字分析法、折叠法、随机数法、乘积法、点积法等。
-
直接定址法:
取关键字本身 / 关键字的某个线性函数值 作为哈希地址。即:Hash(key) = key 或者 Hash(key) = a * key + b,其中 a 和 b 为常数。(适合于关键字分布基本连续的情况) -
除留余数法:
假设哈希表的表长为 m,取一个不大于 m 但接近或等于 m 的质数 p,利用取模运算,将关键字转换为哈希地址。即:Hash(key) = key % p,其中 p 为不大于 m 的质数。(一般 p 取素数或者 m) -
平方取中法:
先通过求关键字平方值的方式扩大相近数之间的差别,然后根据表长度取关键字平方值的中间几位数为哈希地址。 -
基数转换法:
将关键字看成另一种进制的数再转换成原来进制的数,然后选其中几位作为哈希地址。
1.3 哈希冲突
哈希冲突(Hash Collision):不同的关键字通过同一个哈希函数可能得到同一哈希地址,即 key1 ≠ key2,而 Hash(key1) = Hash(key2),这种现象称为哈希冲突。
常用的哈希冲突解决方法主要是两类:「开放地址法(Open Addressing)」 和 「链地址法(Chaining)」。
- 开放地址法:
将哈希表中的「空地址」向处理冲突开放。当哈希表未满时,处理冲突时需要尝试另外的单元,直到找到空的单元为止。
当发生冲突时,开放地址法按照下面的方法求得后继哈希地址:H(i) = (Hash(key) + F(i)) % m,i = 1, 2, 3, …, n (n ≤ m - 1)。
H(i) 是在处理冲突中得到的地址序列;Hash(key) 是哈希函数;m 是哈希表表长;F(i) 是冲突解决方法,取法可以有:线性探测法、二次探测法、伪随机数序列。
- 链地址法:
将具有相同哈希地址的元素(或记录)存储在同一个线性链表中。
字典
在python中,字典就是一个哈希表实现。
https://www.runoob.com/python3/python3-dictionary.html
2 练习(day1)
2.1存在重复元素
描述:给定一个整数数组 nums。
要求:判断是否存在重复元素。如果有元素在数组中出现至少两次,返回 True;否则返回 False。
思路一:使用set() 函数创建一个无序不重复元素集,(可进行关系测试,删除重复数据,还可以计算交集、差集、并集等),若有重复元素,则去重后,列表长度必然小于原列表长度,故判断二者长度是否一致即可。https://www.runoob.com/python3/python3-set.html
思路二:哈希表。将数组中每个元素插入到哈希表中。如果插入一个元素时发现该元素已经存在于哈希表中,则说明存在重复的元素。python中利用字典实现(key是唯一的,但value则不必)。
from typing import List
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
return not len(set(nums)) == len(nums)
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
numdict = {} #建一个空字典
# 哈希表 key 为 nums1 数组中的数,value 为值
for i in nums:
if i not in numdict: #判断nums中的数是否在字典中
numdict[i] = 1 #不在,则将该键添加进字典并赋值1(随便赋值)
else:
return True #在,则有重复,返回T,跳出循环
return False #无重复,返回F
2.2存在重复元素 II
描述:给你一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个不同的索引 i 和 j ,满足 nums[i] == nums[j] 且 abs(i - j) <= k 。如果存在,返回 true ;否则,返回 false 。
思路:遍历数组并记录下标,将数组元素作为key插入字典,下标为value,key重复时计算此时下标差值,<=k则输出T,否则修改对应value为最大下标,继续遍历。
enumerate(seq,start)函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,返回枚举对象(index,seq)
class Solution:
def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
numdict = {}
for i,num in enumerate(nums): #遍历序列的同时,获得索引i
if num in numdict and (i-numdict[num]) <= k:
return True #num在字典中,且两数距离小于k,返回T
numdict[num] = i #不在-添加进字典,在-修改value,value为当前下标
return False #不满足条件,返回F
2.3有效的数独
描述:请你判断一个 9 x 9 的数独是否有效。只需要根据以下规则 ,验证已经填入的数字是否有效即可。
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
空白格用 ‘.’ 表示。
思路:定义三个二维数组,循环遍历九宫格中每个元素,判断是否在每一行每一列每一小格中,在则无效,不在则更新数组
class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
row = [set() for _ in range(9)] #创建行空数组
col = [set() for _ in range(9)] #创建列空数组
block = [set() for _ in range(9)] #创建格子空数组
for i in range(9):
for j in range(9): #遍历九宫格内每个元素
if board[i][j] != '.': #非空白格
num = board[i][j]
if num in row[i] or num in col[j] or num in block[(i//3)*3+(j//3)]:
return False #判断该数在行、列、格子中是否存在,存在则数独无效,返回F
row[i].add(num) #不存在则添加进对应数组
col[j].add(num)
block[(i//3)*3+(j//3)].add(num)
return True #遍历完毕,数独有效,返回T
3练习(day2)
3.1两个数组的交集
描述:给定两个数组 nums1 和 nums2 ,返回它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
思路一:将列表转换成集合,求交集,最后转换回列表输出
思路二:创建一个哈希表用于判断是否有重复,一个列表存储交集
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
return list(set(nums1)&set(nums2))
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
# 有一个数组为空,则交集为空
if not nums1 or not nums2:
return []
hash = {} # 哈希表
res = [] # 结果列表,存放最后结果
# 哈希表 key 为 nums1 数组中的数,value 为值,赋1
for i in nums1:
if i not in hash:
hash[i] = 1
# 遍历 nums2,如果 nums2 数组中的数出现在哈希表中,对应数放入结果列表,对应 value 值置为0
for j in nums2:
if j in hash and hash[j]==1:
res.append(j)
hash[j] = 0
return res
3.2两个数组的交集 II
描述:给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
# 有一个数组为空,则交集为空
if not nums1 or not nums2:
return []
hash = {} # 哈希表
res = [] # 结果列表,存放最后结果
# 哈希表 key 为 nums1 数组中的数,value 为值,赋1
for i in nums1:
if i not in hash:
hash[i] = 1
else:
hash[i]+=1
# 遍历 nums2,如果 nums2 数组中的数出现在哈希表中,对应数放入结果列表,对应 value 值置为0
for j in nums2:
if j in hash and hash[j]>0:
res.append(j)
hash[j]-=1
return res
counter() 函数:Python标准库 collections 里的 counter() 函数是一个计数器工具,用于统计可迭代对象中元素出现的次数,并返回一个字典(key-value)key 表示元素,value 表示各元素 key 出现的次数,可为任意整数 (即包括0与负数)。
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
if len(nums2) < len(nums1):
return self.intersect(nums2, nums1)
m = collections.Counter(nums1)
res = []
for num in nums2:
if num in m:
res.append(num)
m[num] -= 1
if m[num] == 0:
m.pop(num)
return res
3.3设计哈希映射
描述:不使用任何内建的哈希表库设计一个哈希映射(HashMap)。
实现 MyHashMap 类:
MyHashMap() 用空映射初始化对象
void put(int key, int value) 向 HashMap 插入一个键值对 (key, value) 。如果 key 已经存在于映射中,则更新其对应的值 value 。
int get(int key) 返回特定的 key 所映射的 value ;如果映射中不包含 key 的映射,返回 -1 。
void remove(key) 如果映射中存在 key 的映射,则移除 key 和它所对应的 value 。
思路一:超大数组
思路二:拉链法
(1)对于put(key, value)操作:
我们先求出key的哈希值(取模),然后遍历该位置上的链表:
如果链表中包含key,则更新其对应的value;
如果链表中不包含key,则直接将(key,value)插入该链表中。
(2)对于get(key)操作:
求出key对应的哈希值后,遍历该位置上的链表.
如果key在链表中,则返回其对应的value,否则返回-1。
(3)对于remove(key),求出key的哈希值后,遍历该位置上的链表,如果key在链表中,则将其删除。
class MyHashMap:
def __init__(self):
self.map = [-1]*1000001
def put(self, key: int, value: int) -> None:
self.map[key] = value
def get(self, key: int) -> int:
return self.map[key]
def remove(self, key: int) -> None:
self.map[key] = -1
# Your MyHashMap object will be instantiated and called as such:
# obj = MyHashMap()
# obj.put(key,value)
# param_2 = obj.get(key)
# obj.remove(key)
class MyHashMap:
def __init__(self):
self.buckets = 1009 # 开辟一个大数组,长度为质数
self.table = [[] for _ in range(self.buckets)]
def hash(self, key): #求key的哈希值(取模)
return key % self.buckets
def put(self, key: int, value: int) -> None:
hashkey = self.hash(key)
for item in self.table[hashkey]: #遍历哈希到的链表中,查找key,并更新值
if item[0] == key:
item[1] = value
return # 更新完之后,直接返回
self.table[hashkey].append([key, value]) # 如果链表中找不到对应的key,将其新添到链表中
def get(self, key: int) -> int:
hashkey = self.hash(key)
for item in self.table[hashkey]:
if item[0] == key:
return item[1]
return -1 # 可能哈希的位置,所对应的链表不为空,但是不存在该值
def remove(self, key: int) -> None:
hashkey = self.hash(key)
for i, item in enumerate(self.table[hashkey]):
if item[0] == key:
self.table[hashkey].pop(i)
return
作者:负雪明烛
链接:https://leetcode.cn/problems/design-hashmap/solutions/654877/xiang-jie-hashmap-de-she-ji-zai-shi-jian-85k9/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。