LeetCode-哈希表算法总结

引言

哈希表(Hash Table)是一种高效的数据结构,它通过键值对的方式存储数据,并利用哈希函数实现快速的查找、插入和删除操作。本文将深入探讨哈希表的原理、实现方法、应用场景以及相关的解题技巧。

1. 哈希表基本原理

1.1 概念

哈希表是一种基于数组的数据结构,它使用哈希函数将键映射到数组索引,从而实现快速访问。

1.2 哈希函数

哈希函数是哈希表的核心,它将键转换为数组索引。一个好的哈希函数应该:

  • 计算速度快
  • 均匀分布
  • 减少冲突

1.3 处理冲突

当两个不同的键被哈希到同一个索引时,就会发生冲突。常见的解决方法有:

  • 链地址法(Chaining)
  • 开放寻址法(Open Addressing)

2. Python 中的哈希表实现

Python 的字典(dict)就是一种哈希表实现。以下是一个简单的哈希表类实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class HashTable:
def __init__(self, size=100):
self.size = size
self.table = [[] for _ in range(self.size)]

def _hash(self, key):
return hash(key) % self.size

def insert(self, key, value):
index = self._hash(key)
for item in self.table[index]:
if item[0] == key:
item[1] = value
return
self.table[index].append([key, value])

def get(self, key):
index = self._hash(key)
for item in self.table[index]:
if item[0] == key:
return item[1]
raise KeyError(key)

def remove(self, key):
index = self._hash(key)
for i, item in enumerate(self.table[index]):
if item[0] == key:
del self.table[index][i]
return
raise KeyError(key)

3. 哈希表的应用场景

  1. 快速查找和检索
  2. 去重
  3. 缓存实现
  4. 计数器
  5. 数据库索引
  6. 密码存储(配合加盐和加密算法)

4. 哈希表解题技巧

4.1 两数之和

使用哈希表可以将时间复杂度从 O(n^2) 降到 O(n)。

1
2
3
4
5
6
7
8
def two_sum(nums, target):
hash_table = {}
for i, num in enumerate(nums):
complement = target - num
if complement in hash_table:
return [hash_table[complement], i]
hash_table[num] = i
return []

4.2 字母异位词分组

使用排序后的字符串作为键,将异位词分组。

1
2
3
4
5
6
7
8
from collections import defaultdict

def group_anagrams(strs):
groups = defaultdict(list)
for s in strs:
key = ''.join(sorted(s))
groups[key].append(s)
return list(groups.values())

4.3 最长连续序列

使用哈希表优化查找过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def longest_consecutive(nums):
num_set = set(nums)
longest = 0

for num in num_set:
if num - 1 not in num_set:
current_num = num
current_streak = 1

while current_num + 1 in num_set:
current_num += 1
current_streak += 1

longest = max(longest, current_streak)

return longest

4.4 LRU 缓存

结合哈希表和双向链表实现 O(1) 时间复杂度的 LRU 缓存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from collections import OrderedDict

class LRUCache:
def __init__(self, capacity):
self.capacity = capacity
self.cache = OrderedDict()

def get(self, key):
if key not in self.cache:
return -1
self.cache.move_to_end(key)
return self.cache[key]

def put(self, key, value):
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.capacity:
self.cache.popitem(last=False)

5. 哈希表的优化技巧

  1. 选择合适的初始大小: 根据预期数据量选择合适的哈希表初始大小,以减少重新哈希的次数。

  2. 动态扩容: 当哈希表负载因子过高时,增加表的大小并重新哈希所有元素。

  3. 使用质数作为表大小: 这有助于减少哈希冲突。

  4. 使用更好的哈希函数: 根据具体的键类型选择或设计更适合的哈希函数。

  5. 冲突解决: 在高负载情况下,考虑使用更高效的冲突解决方法,如Cuckoo哈希或Robin Hood哈希。

6. 哈希表的局限性

尽管哈希表在许多场景下表现出色,但它也有一些局限性:

  1. 无序性: 哈希表不保持元素的插入顺序。如果需要有序存储,可以考虑使用 OrderedDict。

  2. 空间开销: 为了保持良好的性能,哈希表通常会保持较低的负载因子,这可能导致空间浪费。

  3. 哈希冲突: 虽然有多种方法处理冲突,但在极端情况下,冲突仍可能导致性能下降。

  4. 不适合范围查询: 哈希表不适合进行范围查询,对于此类需求,可能需要考虑使用平衡树等其他数据结构。

结语

哈希表是一种强大而灵活的数据结构,在众多算法和实际应用中扮演着关键角色。掌握哈希表的原理和使用技巧,可以帮助我们更高效地解决各种问题。在实际应用中,要根据具体需求权衡哈希表的优势和局限性,选择最合适的数据结构和算法。