三、哈希表(1)

哈希表理论基础

哈希表都是用来快速判断一个元素是否出现集合里,如果枚举查找,需要O(n),哈希表索引查找,只要O(1)。

  1. 哈希函数:将内容映射为哈希表的索引
  2. 哈希碰撞是指在哈希表中,不同的键通过哈希函数产生了相同的哈希值。由于哈希表使用哈希值来确定键值对存储的位置,所以哈希碰撞需要妥善处理,以避免数据丢失或查找错误。

处理哈希碰撞的常见方法

  1. 开放地址法(Open Addressing)

    • 当发生碰撞时,寻找下一个空闲的存储位置。
    • 常见策略:
      • 线性探测(Linear Probing):如果发生碰撞,则检查下一个位置,依次类推,直到找到空闲位置。
      • 二次探测(Quadratic Probing):如果发生碰撞,检查 (i^2) 位置(i是碰撞次数),减少聚集现象。
      • 双重哈希(Double Hashing):使用第二个哈希函数计算新的存储位置。
  2. 链地址法(Chaining)

    • 每个存储位置维护一个链表,所有产生相同哈希值的键值对都存储在该链表中。
    • 当发生碰撞时,将新的键值对插入到链表中。

示例代码

以下是使用链地址法(Chaining)处理哈希碰撞的简单示例代码:

class HashTable:
    def __init__(self, size):
        self.size = size
        self.table = [[] for _ in range(size)]
    
    def hash_function(self, key):
        return hash(key) % self.size
    
    def insert(self, key, value):
        index = self.hash_function(key)
        for kvp in self.table[index]:
            if kvp[0] == key:
                kvp[1] = value
                return
        self.table[index].append([key, value])
    
    def search(self, key):
        index = self.hash_function(key)
        for kvp in self.table[index]:
            if kvp[0] == key:
                return kvp[1]
        return None
    
    def delete(self, key):
        index = self.hash_function(key)
        for i, kvp in enumerate(self.table[index]):
            if kvp[0] == key:
                del self.table[index][i]
                return

# 示例使用
ht = HashTable(10)
ht.insert("apple", 1)
ht.insert("banana", 2)
ht.insert("orange", 3)

print(ht.search("apple"))  # 输出: 1
print(ht.search("banana"))  # 输出: 2
print(ht.search("grape"))  # 输出: None

ht.delete("banana")
print(ht.search("banana"))  # 输出: None

不同处理方法的优缺点

  1. 开放地址法

    • 优点:不需要额外的存储空间,适合较小的哈希表。
    • 缺点:当负载因子接近1时,查找效率显著下降,需要处理删除操作的复杂性。
  2. 链地址法

    • 优点:即使负载因子高时,查找效率也相对稳定,删除操作简单。
    • 缺点:需要额外的存储空间来维护链表,当很多元素产生相同哈希值时,链表会变得很长。

选择适当的方法

  • 当哈希表较大且冲突概率较低时,开放地址法可以更节省空间。
  • 当哈希表较小或负载因子较高时,链地址法能更好地处理碰撞并保持查找效率。

数据结构

setlistmap(在Python中对应 dict)在哈希表应用中有以下主要区别:

对比

特性setlistdict
存储内容不重复元素有序且可重复元素键值对
实现方式哈希表动态数组哈希表
有序性无序(无特定顺序)有序(按插入顺序)无序(Python 3.7 之前)
有序(Python 3.7 及之后)
重复性不允许重复允许重复键不允许重复
查找复杂度O(1)O(n)O(1)
应用场景快速检查元素是否存在
去重
保留元素顺序
允许重复元素
快速查找、插入和删除
基于键的值
  • 使用 set 来处理无序且不重复的元素。
  • 使用 list 来处理有序且允许重复的元素。
  • 使用 dict 来处理键值对并需要快速查找值的场景。

题目

242.有效的字母异位词
用collections.Counter来计数,返回的是一个字典

class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        # 用collections.Counter来计数,返回的是一个字典
        return Counter(s) == Counter(t)

ord()返回单个字符对应的 Unicode 编码,用数组,索引可以用a~z的 ASCII 值,字符减去a,用两个数组可以优化成一个数组,先加计算s的,再减去t的,看是否等于0,看是否是空数组 生成器+all, 类似的还有any(),(是否有true) filter()(找出所有true的元素,常与lamba连用)

class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
 
        # 用数组,索引可以用a~z的 ASCII 值,字符减去a
        # 用两个数组可以优化成一个数组,先加计算s的,再减去t的,看是否等于0
        cnt = [0]*26
        for c in s:
            cnt[ord(c) - ord('a')] += 1

        for c in t:
            cnt[ord(c) - ord('a')] -= 1

        return all(n == 0 for n in cnt)# 看是否是空数组  生成器+all, 类似的还有any() \filter()

349. 两个数组的交集
无序,去重交集(都出现)。
如果数据量很大,用列表明显不合适,下标放不了那么大的数,用set
什么时候不用列表:(1)数据量大,下标放不下(2)数据分散,因为下标是连续的
法一:set
交集 & 并集 | 差集 -

class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        return list(set(nums1)&set(nums2))

法二:字典 dict

class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        table = {}
        for num in nums1:
            table[num] = table.get(num, 0) + 1

        res = set()
        for num in nums2:
            if num in table:
                res.add(num)
                # del table[num] 由于set是不重复,所以不del也可以
        # print(res)
        # print(list(res)) # 集合直接变成列表
        return list(res)
        

202. 快乐数
即使数字很大,不断平方和之后也会小下来回到 [1,243] 这个范围内,所以是一个环形链表的问题。最终只有:达到循环某个数 or 循环等于1(即快乐数)
注意 函数要么在方法内部定义,定义在引用之前,要么在类内定义为方法,用self.function()调用

class Solution:
    def isHappy(self, n: int) -> bool:
        
        fast = n
        slow = n

        def sumNext(num: int) -> int:
            sum = 0
            temp = num
            while temp > 0:
                dig = temp % 10
                temp = temp // 10
                sum += dig**2

            return sum
            
        # 一直循环,有出现就跳出
        while True:
            fast = sumNext(fast)
            fast = sumNext(fast)
            slow = sumNext(slow)

            if fast == 1:
                return True

            if fast == slow:
                return False

1. 两数之和
找到了一个数,要看他的搭子有没有出现过,(在一些数中找一个数sum-num有没有)看有咩有出现过就用哈希表的方法
数不连续 pass list,集合没有办法找到下标 pass,字典 可以,key是数,value是下标。
map存放的是遍历过的元素。
暴力做法每次拿两个数出来相加,和 target 比较,那么花费 O(1) 的时间,只获取了 O(1) 的信息。 而哈希表做法,每次查询都能知道 O(n) 个数中是否有 target−nums[j],那么花费 O(1) 的时间,就获取了 O(n) 的信息。
注意

  • 判断字典里有没有 用if x in map,如果没有x在map,map[x]报错
    字典添加元素 map[x] = index
  • 判断集合有没有 if x in setA
    集合添加元素 setA.add(x)
  • 两者初始化都是 = { }
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        map = {}

        for idx, x in enumerate(nums):  # 遍历下标和数值
            if target - x in map:
                return [map[target - x], idx]
            map[x] = idx

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值