数据结构与算法之哈希表、映射、集合

270 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 5 天,点击查看活动详情

作者: 千石
支持:点赞、收藏、评论
欢迎各位在评论区交流

前言

本文大纲

哈希表、映射、集合.png

一些话

本文内容来自我平时学习的一些积累,如有错误,还请指正

在题目实战部分,我将代码实现和代码解释设置在了解题思路的下方,方便各位作为参考刷题

题目练习步骤:

  1. 给自己10分钟,读题并思考解题思路
  2. 有了思路以后开始写代码,如果在上一步骤中没有思路则停止思考并且看该题题解
  3. 在看懂题解(暂时没看懂也没关系)的思路后,背诵默写题解,直至能熟练写出来
  4. 隔一段时间,再次尝试写这道题目

正文

前置知识

  1. 哈希表:

    • 实现:通过哈希函数将键映射到桶,存储键值对,具体存储形式可以是链表或者开放寻址法。
    • 特性:查询快,平均时间复杂度O(1);但当哈希冲突较多时,性能会下降,最坏时间复杂度O(n)。
  2. 映射:

    • 实现:通过某种数据结构,如二分搜索树,红黑树,线段树等,存储键值对,并可以根据键值查询对应的值。
    • 特性:查询效率和插入效率都较高,平均时间复杂度O(logn)。
  3. 集合:

    • 实现:通过某种数据结构,如二分搜索树,平衡树,位运算等,存储一组元素,并且元素不能重复。
    • 特性:元素不重复,插入效率和删除效率都较高,平均时间复杂度O(logn)。

解题小窍门

  1. 哈希表:

    • 快速的查找一个元素是否在表中,也可以方便的计数。
    • 如果数据是字符串或者数字,那么哈希表可以快速的对数据进行分类。
    • 用来去重。
  2. 映射:

    • 快速的查找一个元素的值,以及更新元素的值。
    • 用映射来统计每一个元素出现的次数。
  3. 集合:

    • 快速的查找一个元素是否在集合中,也可以快速的插入一个元素。
    • 解决问题中的并集、交集等关系。
    • 用来去重,也可以用来判断两个集合的交集是否为空。

实战

题目一:242. 有效的字母异位词 - 力扣(LeetCode)

image.png

做题前可以思考

如果题目没有给出明确的限制条件

  1. 思考异位词的具体含义
  2. 考虑是否会有大小写敏感

解题思路

  1. 暴力法

首先将两个字符串转换为列表,并对其中每一个字符进行排序,然后再将两个列表比较,如果相同,则说明是字母异位词,否则不是。

复杂度分析:

因为使用了 sorted() 函数,该函数的复杂度为 O(nlogn)。此外,比较字符的复杂度为 O(n),因此整个算法的复杂度为 O(nlogn + n) = O(nlogn)。

  1. 哈希表

使用哈希表,统计每一个字符在字符串 s 和 t 中出现的频次,如果最终两个哈希表相同,则说明两个字符串是字母异位词。

复杂度分析:

因为使用了哈希表,在每次字符的查找和修改操作的复杂度为 O(1),因此整个算法的复杂度为 O(n)。

代码实现

  1. 暴力法
def isAnagram(s, t):
    if len(s) != len(t):
        return False
    s = sorted(s)
    t = sorted(t)
    for i in range(len(s)):
        if s[i] != t[i]:
            return False
    return True
  1. 哈希表
def isAnagram(s, t):
    if len(s) != len(t):
        return False
    hashmap = {}
    for i in s:
        if i not in hashmap:
            hashmap[i] = 1
        else:
            hashmap[i] += 1
    for i in t:
        if i not in hashmap:
            return False
        else:
            hashmap[i] -= 1
    for i in hashmap:
        if hashmap[i] != 0:
            return False
    return True

题目二:49. 字母异位词分组 - 力扣(LeetCode)

image.png

解题思路

  1. 排序+Hash表

对每个字符串排序,使用排序后的字符串作为key,将其原始字符串存储在Hash表中,如果排序后的字符串已经存在,将原始字符串加入该key对应的list中。最后,将所有的value值拼接起来,组成答案。

复杂度分析:

  • 排序操作:O(nlogn)
  • Hash表插入操作:O(n)
  • 整体复杂度:O(nlogn + n) = O(nlogn)
  1. 计数+Hash表

对每个字符串的每个字符,计数其出现的次数,使用计数的结果作为key,将其原始字符串存储在Hash表中,如果计数的结果已经存在,将原始字符串加入该key对应的list中。最后,将所有的value值拼接起来,组成答案。

复杂度分析:

  • 计数操作:O(n)
  • Hash表插入操作:O(n)
  • 整体复杂度:O(n + n) = O(n)

代码实现

  1. 排序+Hash表
from collections import defaultdict

def groupAnagrams(strs):
    dic = defaultdict(list)
    for s in strs:
        dic[''.join(sorted(s))].append(s)
    return list(dic.values())
  1. 计数+Hash表
from collections import defaultdict

def groupAnagrams(strs):
    dic = defaultdict(list)
    for s in strs:
        count = [0] * 26
        for c in s:
            count[ord(c) - ord('a')] += 1
        dic[tuple(count)].append(s)
    return list(dic.values())

题目三:Loading Question... - 力扣(LeetCode)

image.png

解题思路

这道题目在之前的文章中涉及过,这里讲一种新的思路:哈希表

使用哈希表,存储每个数的下标,遍历数组,判断target减去当前数是否存在,若存在,则该数与target减去该数即为答案。

复杂度分析: 遍历数组的时间复杂度为O(n),查询哈希表的时间复杂度为O(1),故总的时间复杂度为O(n)。

代码实现

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        dic = {}
        for i in range(len(nums)):
            if target - nums[i] in dic:
                return [dic[target - nums[i]], i]
            dic[nums[i]] = i

代码解释

代码实现的步骤如下:

  1. 声明哈希表 dic,存储每个数的下标。
  2. 遍历数组 nums,对于每个数字 nums[i],判断 target - nums[i] 是否在哈希表 dic 中,若存在,说明当前数字与 target - nums[i] 相加等于 target。
  3. 返回当前数字与 target - nums[i] 在数组中的下标,即为答案。
  4. 若不存在,则将当前数字 nums[i] 和下标存入哈希表 dic 中。

Tips:代码中需要注意的是,哈希表 dic 的键存储的是数字,值存储的是下标,方便在第二步判断。

总结

本文介绍了哈希表、映射、集合的实现、特性和使用时的小技巧,并且介绍了几道题目,希望能帮助读者更好的理解三者