赞
踩
散列表(hash table),又名‘hash表’,它用的是数组支持按照下标随机访问数据(时间复杂度O(1))的特性,所以散列表其实就是基于数组结构的一种扩展。简单的来说,就是把键值通过散列函数求得hash值之后,对数组容量进行取模运算,得到存放在数组位置的下标值,当我们按照键值查询元素时,我们用同样的方法将键值转化数组下标,从对应的数组下标的位置取数据。散列表这种数据结构虽然支持非常高效的数据插入、删除、查找操作,但是散列表中的数据都是通过散列函数打乱之后无规律存储的。也就说,它无法支持按照某种顺序快速地遍历数据。如果希望按照顺序遍历散列表中的数据,那我们需要将散列表中的数据拷贝到数组中,然后排序,再遍历。因为散列表是动态数据结构,不停地有数据的插入、删除,所以每当我们希望按顺序遍历散列表中的数据的时候,都需要先排序,那效率势必会很低。为了解决这个问题,我们常常会将散列表和链表(或者跳表)结合在一起使用。
散列表(Hash table,也叫哈希表),是根据关键码值(Key和value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
哈希表的两个核心问题是:「哈希函数的构建」 和 「哈希冲突的解决方法」。
常用的哈希函数方法有:直接定址法、除留余数法、平方取中法、基数转换法、数字分析法、折叠法、随机数法、乘积法、点积法等。
常用的哈希冲突的解决方法有两种:开放地址法和链地址法。
有两种不同类型的哈希表:哈希集合和哈希映射。
在标准模板库的帮助下,哈希表是易于使用的。大多数常见语言(如Java,C ++ 和 Python)都支持哈希集合和哈希映射。通过选择合适的哈希函数,哈希表可以在插入和搜索方面实现出色的性能。
Python 将哈希表用于字典和集合。 哈希表是键值对的无序集合,其中每个键都是唯一的。 哈希表提供了有效的查找,插入和删除操作的组合。 这些是数组和链接列表的最佳属性。相比于列表和元组,字典的性能更优,特别是对于查找、添加和删除操作,字典都能在常数时间复杂度内完成。而集合和字典基本相同,唯一的区别,就是集合没有键和值的配对,是一系列无序的、唯一的元素组合。
哈希表是一种数据结构,它使用哈希函数组织数据,以支持快速插入和搜索。哈希表的核心思想就是使用哈希函数将键映射到存储桶。更确切地说:
下面举一个简单的例子,我们来理解下:
在示例中,我们使用 y = x % 5 y = x % 5 y=x%5 作为哈希函数。让我们使用这个例子来完成插入和搜索策略:
哈希函数是哈希表中最重要的组件,该哈希表用于将键映射到特定的桶。在之前的示例中,我们使用 y = x y = x % 5 y=x 作为散列函数,其中 x x x 是键值, y y y 是分配的桶的索引。
散列函数将取决于键值的范围和桶的数量。下面是一些哈希函数的示例:
哈希函数的设计是一个开放的问题。其思想是尽可能将键分配到桶中,理想情况下,完美的哈希函数将是键和桶之间的一对一映射。然而,在大多数情况下,哈希函数并不完美,它需要在桶的数量和桶的容量之间进行权衡。
当然,我们也可以自定义一些哈希函数。一般的方法有:
理想情况下,如果我们的哈希函数是完美的一对一映射,我们将不需要处理冲突。不幸的是,在大多数情况下,冲突几乎是不可避免的。例如,在我们之前的哈希函数(y = x % 5)中,1987 和 2 都分配给了桶 2,这就是一个哈希冲突。
解决哈希冲突应该要思考以下几个问题:
那么一旦发生冲突,我们该如何解决呢?常用的方法有两种:开放定址法和链地址法。
1. 开放定址法
即当一个关键字和另一个关键字发生冲突时,使用某种探测技术在哈希表中形成一个探测序列,然后沿着这个探测序列依次查找下去。当碰到一个空的单元时,则插入其中。
常用的探测方法是线性探测法。 比如有一组关键字 {12,13,25,23},采用的哈希函数为 key % 11。当插入 12,13,25 时可以直接插入,地址分别为 1、2、3。而当插入 23 时,哈希地址为 23 % 11 = 1。
然而,地址 1 已经被占用,因此沿着地址 1 依次往下探测,直到探测到地址 4,发现为空,则将 23 插入其中。如下图所示:
2. 链地址法
将哈希地址相同的记录存储在一张线性链表中。例如,有一组关键字 {12,13,25,23,38,84,6,91,34},采用的哈希函数为 key % 11。如下图所示:
1. 哈希表的基本操作
在很多高级语言中,哈希函数、哈希冲突都已经在底层完成了黑盒化处理,是不需要开发者自己设计的。也就是说,哈希表完成了关键字到地址的映射,可以在常数级时间复杂度内通过关键字查找到数据。
至于实现细节,比如用了哪个哈希函数,用了什么冲突处理,甚至某个数据记录的哈希地址是多少,都是不需要开发者关注的。接下来,我们从实际的开发角度,来看一下哈希表对数据的增删查操作。
哈希表中的增加和删除数据操作,不涉及增删后对数据的挪移问题(数组需要考虑),因此处理就可以了。
哈希表查找的细节过程是:对于给定的 key,通过哈希函数计算哈希地址 H (key)。
如果哈希地址对应的值为空,则查找不成功。反之,则查找成功。虽然哈希表查找的细节过程还比较麻烦,但因为一些高级语言的黑盒化处理,开发者并不需要实际去开发底层代码,只要调用相关的函数就可以了。
2. 哈希表的优缺点
优势:它可以提供非常快速的插入-删除-查找操作,无论多少数据,插入和删除值需要接近常量的时间。在查找方面,哈希表的速度比树还要快,基本可以瞬间查找到想要的元素。
不足:哈希表中的数据是没有顺序概念的,所以不能以一种固定的方式(比如从小到大)来遍历其中的元素。在数据处理顺序敏感的问题时,选择哈希表并不是个好的处理方法。同时,哈希表中的key 是不允许重复的,在重复性非常高的数据中,哈希表也不是个好的选择。
705. 设计哈希集合
题目描述
# 定义一个一维长度为 buckets 的二维数组 table。第一维度用于计算哈希函数,为 key 分桶。 # 第二个维度用于寻找 key 存放的具体位置。第二维度的数组会根据 key 值动态增长,模拟真正的链表。 class MyHashSet: def __init__(self): self.buckets = 1009 self.table = [[] for _ in range(self.buckets)] def hash(self, key): return key % self.buckets # 用取余数的方法实现集合 def add(self, key): # 向哈希集合中插入一个值 hashkey = self.hash(key) if key in self.table[hashkey]: return self.table[hashkey].append(key) def remove(self, key): # 将给定值从哈希集合中删除。如果哈希集合中没有这个值,什么也不做 hashkey = self.hash(key) if key not in self.table[hashkey]: return self.table[hashkey].remove(key) def contains(self, key): # 返回哈希集合中是否存在这个值 hashkey = self.hash(key) return key in self.table[hashkey]
706. 设计哈希映射
题目描述
# 最简单的思路就是用模运算作为哈希方法,为了降低哈希碰撞的概率,通常取素数的模,例如 模 1009或2069 class MyHashMap: def __init__(self): self.buckets = 1009 self.table = [[] for _ in range(self.buckets)] def hash(self, key): # 哈希映射关系 return key % self.buckets def put(self, key: int, value: int) -> None: # 向哈希映射中插入(键,值)的数值对。如果键对应的值已经存在,更新这个值 hashkey = self.hash(key) for item in self.table[hashkey]: if item[0] == key: item[1] = value return self.table[hashkey].append([key, value]) def get(self, key: int) -> int: # 返回给定的键所对应的值,如果映射中不包含这个键,返回-1 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
136. 只出现一次的数字
题目描述:给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
class Solution: def singleNumber(self, nums: List[int]) -> int: #一、用字典统计元素个数 # hash_map = Counter(nums) hash_map = {} #方法一 for i in nums: hash_map[i] = hash_map.get(i, 0) + 1 #方法二 # for i in nums: # if i not in hash_map: # hash_map[i] = 1 # else: # hash_map[i] += 1 #二、找出只出现一次的元素 #方法一 return list(hash_map.keys())[list(hash_map.values()).index(1)] #方法二 # new_hash_map = {v:k for k,v in hash_map.items()} # return new_hash_map[1] #方法三 # return [key for key, value in hash_map.items() if value == 1] # 或 # for key,value in hash_map.items(): # if value == 1: # return key
137. 只出现一次的数字 II
题目描述:给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。
class Solution:
def singleNumber(self, nums: List[int]) -> int:
hash_map = Counter(nums)
return min(hash_map.items(), key=lambda x:x[1])[0]
260. 只出现一次的数字 III
题目描述:给你一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。
class Solution:
def singleNumber(self, nums: List[int]) -> List[int]:
hash_map = collections.Counter(nums)
res = []
for key, val in hash_map.items():
if val == 1:
res.append(key)
return res
217. 存在重复元素
题目描述:给你一个整数数组 nums。如果任一值在数组中出现 至少两次,返回 true;如果数组中每个元素互不相同,返回 false。
1. 哈希字典
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
hash_map = {}
for num in nums:
if num in hash_map:
hash_map[num] += 1
else:
hash_map[num] = 1
for index in hash_map:
if hash_map[index] >= 2:
return True
return False
2. 哈希集合
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
hash_set = set()
for num in nums:
if num in hash_set:
return True
else:
hash_set.add(num)
return False
3. 集合的性质
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
# return sum(nums) != sum(set(nums)) or nums.count(0) > 1
return len(nums) != len(set(nums))
219. 存在重复元素 II
题目描述:给你一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个不同的索引 i 和 j ,满足 nums[i] == nums[j] 且 a b s ( i − j ) < = k abs(i - j) <= k abs(i−j)<=k。如果存在,返回 true;否则,返回 false 。
1. 哈希字典
# 找到重复元素和其索引
class Solution:
def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
hash_map = {}
for i in range(len(nums)):
# 已经存在重复的情况
if nums[i] in hash_map and abs(i - hash_map[nums[i]]) <= k:
return True
else:
hash_map[nums[i]] = i
return False
2. 哈希集合
# 维护一个长度为 k 的集合
class Solution:
def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
hash_set = set()
for i in range(len(nums)):
# 存在重复元素
if nums[i] in hash_set:
return True
hash_set.add(nums[i])
# 及时删除超出数组长度的元素
if len(hash_set) > k:
hash_set.remove(nums[i - k])
return False
220. 存在重复元素 III
题目描述:给你一个整数数组 nums 和两个整数 k 和 t 。请你判断是否存在 两个不同下标 i 和 j,使得 a b s ( n u m s [ i ] − n u m s [ j ] ) < = t abs(nums[i] - nums[j]) <= t abs(nums[i]−nums[j])<=t,同时又满足 a b s ( i − j ) < = k abs(i - j) <= k abs(i−j)<=k。如果存在则返回 true,不存在返回 false。
class Solution: def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool: bucket_dict = dict() for i in range(len(nums)): # 将nums[i]划分到大小为 t + 1 的不同桶中,分桶操作 num = nums[i] // (t + 1) # print(num) # 如果桶中已经有元素,有相同的分桶结果,表示存在相同元素 if num in bucket_dict: return True # 将 nums[i] 放入桶中 bucket_dict[num] = nums[i] # print(bucket_dict) # 判断左侧桶是否满足条件 if (num - 1) in bucket_dict and abs(bucket_dict[num - 1] - nums[i]) <= t: return True # 判断右侧桶是否满足条件 if (num + 1) in bucket_dict and abs(bucket_dict[num + 1] - nums[i]) <= t: return True # 将 i - k 之前的旧桶清除,因为之前的桶已经不满足条件了 if i >= k: bucket_dict.pop(nums[i-k] // (t + 1)) return False
349. 两个数组的交集
题目描述:给定两个数组 nums1 和 nums2,返回它们的交集。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序。
1. 集合的交集
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
l1 = set(nums1)
l2 = set(nums2)
return list(l1 & l2)
2. 哈希字典
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
hash_map = dict()
result = []
for num1 in nums1:
if num1 not in hash_map:
hash_map[num1] = 1 # 只做一次计数
for num2 in nums2:
if num2 in hash_map and hash_map[num2] != 0:
hash_map[num2] -= 1 # 及时对结果进行处理
result.append(num2)
return result
350. 两个数组的交集 II
题目描述:给你两个整数数组 nums1 和 nums2,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
hash_map = {}
result = []
for num1 in nums1:
if num1 in hash_map:
hash_map[num1] += 1
else:
hash_map[num1] = 1
for num2 in nums2:
if num2 in hash_map and hash_map[num2] != 0:
hash_map[num2] -= 1
result.append(num2)
return result
36. 有效的数独
请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。
class Solution: def isValidSudoku(self, board: List[List[str]]) -> bool: # 记录行数据、列数据、3x3格子数据,用于标记 1-9 共10个数字 rows = [[0 for _ in range(10)] for _ in range(10)] columns = [[0 for _ in range(10)] for _ in range(10)] boxes = [[0 for _ in range(10)] for _ in range(10)] for i in range(9): for j in range(9): if board[i][j] != '.': # 1-9数字是否重复出现 num = int(board[i][j]) board_index = (i // 3) * 3 + j // 3 # 方格角标的计算用 box[(i/3)*3+(j/3)][n] 来表示 if rows[i][num] > 0 or columns[j][num] > 0 or boxes[board_index][num] > 0: return False rows[i][num] = 1 columns[j][num] = 1 boxes[board_index][num] = 1 return True
389. 找不同
题目描述:给定两个字符串 s 和 t,它们只包含小写字母。字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。请找出在 t 中被添加的字母。
1. 哈希字典
class Solution:
def findTheDifference(self, s: str, t: str) -> str:
return list(Counter(t) - Counter(s))[0]
2. 异或运算
class Solution:
def findTheDifference(self, s: str, t: str) -> str:
return chr(reduce(xor, map(ord, s + t)))
496. 下一个更大元素 I
题目描述
class Solution: def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]: hash_map = {} # 字典存储结果 stack = [] # 列表模拟栈,单调栈 for i in range(len(nums2)): # 遍历每个字符串 if not stack: # 如果当前栈为空,则直接入栈 stack.append(nums2[i]) else: if nums2[i] < stack[-1]: # 当前值小于栈顶元素,则入栈 stack.append(nums2[i]) elif nums2[i] > stack[-1]: # 当前值大于栈顶元素,不停出栈,把所有栈顶key值的value赋值为当前值 while stack and stack[-1] < nums2[i]: hash_map[stack[-1]] = nums2[i] stack.pop() stack.append(nums2[i]) # 当前值入队列 while stack: # 如果栈中还有元素,则全部赋值为-1,表示右边没有更大值 hash_map[stack[-1]] = -1 stack.pop() result = [] for i in nums1: # 返回每个key值对应的value result.append(hash_map[i]) return result
哈希表暂时告一段落,后面在学习中持续补充,谢谢大家的鼓励和支持!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。