LeetCode 第一题 "两数之和" (Two Sum) 问题
分析过程:
这个问题可以通过多种方法解决,包括暴力解法和使用哈希表的解法。以下是详细的分析过程:
-
暴力解法:
- 遍历数组中的每一对元素,检查它们的和是否等于目标值。
- 时间复杂度是 O(n^2),其中 n 是数组的长度。
-
使用哈希表:
- 使用一个哈希表来存储数组中的每个元素及其对应的索引。
- 在遍历数组时,检查当前元素与目标值之差是否存在于哈希表中。
- 如果存在,说明找到了两个元素,它们的和等于目标值。
哈希表解法的实现:
def two_sum(nums, target):
# 创建一个哈希表(字典)来存储数组中的每个元素及其对应的索引
num_to_index = {}
# 遍历数组
for index, num in enumerate(nums):
# 计算当前元素与目标值之差
complement = target - num
# 检查差值是否存在于哈希表中
if complement in num_to_index:
# 如果存在,返回当前元素的索引和差值对应的索引
return [num_to_index[complement], index]
# 将当前元素及其索引存入哈希表
num_to_index[num] = index
# 如果没有找到符合条件的两个元素,返回空列表
return []
# 示例
nums = [2, 7, 11, 15]
target = 9
print(two_sum(nums, target)) # 输出:[0, 1]
详细步骤:
- 初始化一个空的哈希表
num_to_index
。 - 遍历数组
nums
,对于每个元素num
及其索引index
:- 计算
complement = target - num
。 - 检查
complement
是否在哈希表num_to_index
中:- 如果在,返回
[num_to_index[complement], index]
。 - 如果不在,将
num
及其index
存入哈希表num_to_index
。
- 如果在,返回
- 计算
- 如果遍历结束后仍未找到符合条件的元素对,返回空列表。
这种方法的时间复杂度是 O(n),空间复杂度也是 O(n),比暴力解法更高效。
哈希表的基本概念:
哈希表(Hash Table)是一种高效的数据结构,主要用于快速查找、插入和删除操作。哈希表使用键-值对(key-value pairs)来存储数据,通过一个称为哈希函数(hash function)的函数将键映射到表中的一个位置(数组的索引)。这种映射使得查找和插入操作的平均时间复杂度接近于 O(1)。
-
哈希函数(Hash Function):
- 哈希函数将输入的键转换为数组的索引。
- 一个好的哈希函数应当能将键均匀地分布到哈希表中,以减少冲突。
-
冲突(Collision):
- 当两个不同的键被哈希函数映射到相同的索引时,称为冲突。
- 解决冲突的方法有多种,包括链地址法(Separate Chaining)和开放地址法(Open Addressing)。
-
负载因子(Load Factor):
- 负载因子是哈希表中已用槽的比例,计算方法是
已存储元素个数 / 哈希表的总槽数
。 - 负载因子过高时,冲突增多,性能下降;负载因子过低时,空间浪费。
- 负载因子是哈希表中已用槽的比例,计算方法是
哈希表的实现方法:
-
链地址法(Separate Chaining):
- 每个哈希表的槽(bucket)都是一个链表。
- 当冲突发生时,新的元素被插入到链表的末尾。
- 查找时,需要遍历该链表以找到目标元素。
- 在 Python 中,字典(dict)和集合(set)的实现中使用了链地址法。
# 链地址法示例 hash_table = [[] for _ in range(10)] # 创建一个包含10个槽的哈希表,每个槽是一个空列表 def insert(hash_table, key, value): index = hash(key) % len(hash_table) hash_table[index].append((key, value)) def get(hash_table, key): index = hash(key) % len(hash_table) for k, v in hash_table[index]: if k == key: return v return None # 插入和查找示例 insert(hash_table, 'apple', 1) insert(hash_table, 'banana', 2) print(get(hash_table, 'apple')) # 输出: 1 print(get(hash_table, 'banana')) # 输出: 2
-
开放地址法(Open Addressing):
- 当冲突发生时,使用探测(probing)的方法在哈希表中寻找下一个可用槽。
- 常见的探测方法有线性探测(Linear Probing)、二次探测(Quadratic Probing)和双重散列(Double Hashing)。
# 开放地址法示例(线性探测) hash_table = [None] * 10 # 创建一个包含10个槽的哈希表 def insert(hash_table, key, value): index = hash(key) % len(hash_table) while hash_table[index] is not None: index = (index + 1) % len(hash_table) hash_table[index] = (key, value) def get(hash_table, key): index = hash(key) % len(hash_table) while hash_table[index] is not None: k, v = hash_table[index] if k == key: return v index = (index + 1) % len(hash_table) return None # 插入和查找示例 insert(hash_table, 'apple', 1) insert(hash_table, 'banana', 2) print(get(hash_table, 'apple')) # 输出: 1 print(get(hash_table, 'banana')) # 输出: 2
哈希表的优缺点:
优点:
- 快速的查找、插入和删除操作,平均时间复杂度为 O(1)。
- 适用于需要频繁查找操作的数据场景。
缺点:
- 需要一个好的哈希函数来减少冲突。
- 哈希表的性能在最坏情况下(所有键都冲突)可能退化到 O(n)。
- 哈希表需要更多的内存来存储空槽和解决冲突的数据结构。
Python 中的哈希表:
在 Python 中,字典(dict)和集合(set)都是通过哈希表来实现的。它们提供了快速的查找、插入和删除操作,非常适合需要高效数据存取的场景。
# 字典示例
my_dict = {'apple': 1, 'banana': 2}
print(my_dict['apple']) # 输出: 1
my_dict['cherry'] = 3 # 插入新元素
print(my_dict['cherry']) # 输出: 3
# 集合示例
my_set = {'apple', 'banana'}
print('apple' in my_set) # 输出: True
my_set.add('cherry') # 插入新元素
print(my_set) # 输出: {'cherry', 'banana', 'apple'}
'enumerate'函数
在前面 two_sum
函数的实现中,enumerate
用于遍历 nums
列表,同时获取每个元素的索引和值,这样就可以在哈希表中存储元素值及其对应的索引,并在查找时使用。
enumerate
是 Python 内置函数,用于遍历序列(如列表、元组或字符串)时获取元素及其对应的索引。它返回一个迭代器,每次迭代时生成一个包含两个元素的元组:当前元素的索引和值。这个函数通常在需要索引和元素值的场景中非常有用,比如在循环中。
语法:
enumerate(iterable, start=0)
iterable
:一个可以迭代的对象,比如列表、元组、字符串等。start
:可选参数,指定索引的起始值,默认从 0 开始。
示例:
# 示例列表
fruits = ['apple', 'banana', 'cherry']
# 使用 enumerate 获取索引和值
for index, fruit in enumerate(fruits):
print(f"Index: {index}, Fruit: {fruit}")
输出:
Index: 0, Fruit: apple
Index: 1, Fruit: banana
Index: 2, Fruit: cherry
enumerate
的更多用法:
-
指定起始索引:
可以通过设置start
参数来改变起始索引的值。for index, fruit in enumerate(fruits, start=1): print(f"Index: {index}, Fruit: {fruit}")
输出:
Index: 1, Fruit: apple Index: 2, Fruit: banana Index: 3, Fruit: cherry
-
与其他迭代器结合:
enumerate
可以与其他迭代器结合使用,例如列表生成器、集合等。fruits_set = {'apple', 'banana', 'cherry'} for index, fruit in enumerate(fruits_set): print(f"Index: {index}, Fruit: {fruit}")
输出的顺序可能与输入顺序不同,因为集合是无序的。