还不知道什么是哈希表,看这篇文章就够了

前言

一文带你了解哈希表的基础,并且其经常考察的思维算法。本文用于记录自己的学习过程,同时向大家进行分享相关的内容。本文内容参考于 代码随想录 同时包含了自己的许多学习思考过程,如果有错误的地方欢迎批评指正!

哈希表的基础知识

什么是哈希表,数组实质上可以说也就是一个哈希表。那么哈希表有什么作用呢,就是可以快速的通过下标访问其是否存在,可能这么说有点难以理解。举个例子就是在学校里面,我们想确定某个学生是否存在,那么正常的肯定就是枚举法,遍历全部来确定是否有这个学生,其时间复杂度为O($n$),但是如果有了哈希表,我们就可以直接通过访问下标来确定是否有该学生了。

image-20250409093151181

看到这,相信有些人就会有疑问了,通过下标来访问,可是数组的索引都是数字呐,但我们访问的学生名称都是文字字符串一类的,这怎么直接通过下标访问呢?这个时候就有一个非常重要的东西了,哈希函数。我们通过哈希函数可以将学生的名称映射为数组的下标,这样我们就可以直接通过访问数组下标来确定是否存在某个学生了。

image-20250409094920540

这时又会出现一个问题?如果不同的两个学生经过哈希函数映射到了同一个位置怎么办?或者学生的个数大于数组的个数怎么办?这种现象叫做哈希碰撞。解决哈希碰撞的有两种方法:拉链法和线性探测法。

  • 拉链法

当两个学生名字在同个索引发生冲突时,我们可以用链表的方式往后延伸,将其放在同个索引下,后续通过链表的方式访问。这里数据datasize和哈希表的长度tablesize需要注意,我们要选择好哈希表的长度,这样既不会因为太大,空了太多位置而浪费空间,也不会因为太小导致链表过长,影响访问时间。这样我们通过哈希函数转化为索引后,遍历其索引后续的链表即可确定是否存在。

image-20250409095853380

  • 线性探测法

线性探测法与拉链法不同,我们遇到冲突说明该位置被存放,那我们可以往下个索引走,找到一个空的位置来存放冲突的信息。这里就需要注意tablesize一定要大于datasize了,要不然我们就没有位置来存放冲突的数据了。那我们如何查询呢?通过哈希函数转化为索引,看是否该学生,不是则往下找,一直找到空的位置为止,如果还没有找到该学生,说明该学生不存在。

image-20250409100258081

哈希表的三种结构及其考察题目

数组

什么时候用数组作为哈希表呢?就是当元素的个数是确定的,并且其数量并不会很大的时候,我们可以充分的利用数组的空间来当哈希表。如下该题,这道题目中只包含了小写字母,所以我们可以用数组作为哈希表来解决这道题。

[有效的字母异位词](242. 有效的字母异位词 - 力扣(LeetCode))

image-20250409101029956

相关技巧:我们可以以一个数组来存储s中出现的字母,然后以该数组减去t中出现的字母,当数组不为零的时候说明其并非字母异位词。

class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        record = [0] * 26
        for i in s:
            #并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
            record[ord(i) - ord("a")] += 1
        for i in t:
            record[ord(i) - ord("a")] -= 1
        for i in range(26):
            if record[i] != 0:
                #record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。
                return False
        return True

集合(set)

什么时候用集合?就是当元素的个数不确定的时候,或者用数组来表示会有很多的空间被浪费的时候。我们要知道集合的性质哈,无序性和唯一性。就是当我们只需要确实某个元素是否存在时可用,不需要考虑其个数的多少。

[两个数组的交集](349. 两个数组的交集 - 力扣(LeetCode))

image-20250409102105022

相关技巧:审题可以看出元素唯一,返回交集即可无需顺序,这不是正好很符合集合作为哈希表的性质嘛。

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]
        
        return list(res)

映射(map)

什么时候用map?数组的限制是大小受限,set是一个存储key值的结构,无法表明位置个数多方面信息。而map是一种<key,value>的结构。能够存储更多的信息,当然有些人问了,map感觉可以解决所以哈希表的问题,那么为什么不都用map呢?从结构也能看出,map的空间消耗会更大点,有些时候不如数组和set简单明了。

[两数之和](1. 两数之和 - 力扣(LeetCode))

image-20250409102728546

相关技巧:暴力解法就不说了,两个for循环时间复杂度O($n^2$).我们来看怎么用map解决。我们将比较每个数,其map中是否有需要的另一个数,没有则存入map中,找到直接返回即可。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        records = dict()

        for index, value in enumerate(nums):  
            if target - value in records:   # 遍历当前元素,并在map中寻找是否有匹配的key
                return [records[target- value], index]
            records[value] = index    # 如果没找到匹配对,就把访问过的元素和下标加入到map中
        return []
posted @ 2025-04-09 10:42  carpell  阅读(349)  评论(0)    收藏  举报