当前位置: 首页 > news >正文

LeetCode 热题 100 题解(1):哈希

从今天起,我们开始以 python 语言为例,从入门开始解析 LeetCode 热题 100 题单(LeetCode 热题 100 - 学习计划 - 力扣(LeetCode)全球极客挚爱的技术成长平台)。第一个板块是哈希。

一、什么是哈希表(Hash Table)

哈希表是一种高效的键值对存储结构,它利用哈希函数将任意类型的键转换为数组索引,从而实现平均 O(1) 时间复杂度的查找、插入和删除操作。这一快速访问的核心机制在于:通过哈希函数计算键对应的哈希值,再将其映射到数组的特定位置。对比一般的只能用数字下标查找的数组,哈希表可以用字符串、数字、元组等任意可哈希不可变类型作为查找标识。

值得一提的是,不可变类型指的是数据一旦创建就不能原地修改,想要改只能新建一份新对象的数据,而可变类型的数据可以直接修改内存中的内容,无需更换存储地址

# 列表可变,原地修改,地址不变 lst = [5] print(id(lst)) lst[0] = 3 print(id(lst)) # 地址完全一样,修改的是同一个列表内部内容 # 数字不可变,只能更换指向,地址必变 a = 5 print(id(a)) a = 3 print(id(a)) # 地址更换,换了一个全新数字对象

结果:

此处对于哈希表的底层原理不做过多介绍,重点探讨哈希在题目中的具体应用。python 中有两大常用哈希容器:字典 dict 和集合 set。

1.字典 dict

字典的存储形式是 {key,value},是典型的键值对映射一一对应关系。其中key 必须是不可变可哈希类型(数字、字符串、元组),列表和字典不能作为 key。value 则无类型限制,任意数据都能存放。这种结构常用于映射存储分组归类等场景。

2.集合 set

集合的存储格式为{元素1,元素2,...},仅存储元素值,不包含键值对。集合具有自动去重特性,且只允许存储不可变类型的元素不支持通过下标获取元素。使用 x in set 进行成员检测时,平均时间复杂度为O(1)。典型应用场景包括数据去重快速判断元素是否存在

二、例题

1.两数之和

解法1:暴力法

这是一道经典的哈希入门题。第一眼读题容易想到暴力两层循环遍历:外层遍历第一个数 nums[i],内层遍历它之后所有数 nums[j],判断 nums[i] + nums[j] == target,满足直接返回 [i,j]。

class Solution(object): def twoSum(self, nums, target): n = len(nums) for i in range(n): for j in range(i+1, n): if nums[i] + nums[j] == target: return [i, j] return []

这种算法的时间复杂度是 O(n^2),空间复杂度 O(1),显然数据量大时会有超时问题。所以我们考虑用哈希字典对运行时间进行优化。

解法2:字典

问题本质是根据目标值 target 找补值,如果把遍历过的数字以数值:下标的键值对形式存到字典中,只要补数已经遍历过,我们就能直接取出对应下标,实现时间复杂度 O(1) 的查询。

顺着这个思路我们规划算法步骤:

  1. 初始化空字典 hash_map;
  2. 循环遍历数组,同步拿到当前值 value 和下标 idx;
  3. 计算补数 need = target - value;
  4. 判断补数是否存在于字典:存在:直接返回 [ hash_map[need], idx];不存在:把当前 value:idx 存入字典,继续循环。
class Solution(object): def twoSum(self, nums, target): """ :type nums: List[int] :type target: int :rtype: List[int] """ hash_map = {} for idx, val in enumerate(nums): need = target - val if need in hash_map: self = [hash_map[need], idx] hash_map[val] = idx return self

注意题目要求不重复使用同一个元素,因此要先查补数,再存入当前数字,这样字典里永远只保存当前下标之前的元素,不会取到自身。

如此时间复杂度 O(n),数组仅遍历 1 次,字典查询为常数时间;空间复杂度 O(n),最坏情况字典存储全部数组元素。这体现了哈希空间换时间的核心思想。

2.字母异位词分组

此题可以帮助我们理解哈希字典的分组归类功能,任务本质是给每一类异位词生成唯一标识 key,之后我们将 key 相同的字符串存入同一个 value 列表,最终取出所有分组。

如何生成唯一的 key 呢?注意到每组词语的字母类别及对应数量相同,区别是顺序不同。因此我们可以考虑给字母重新排序得到统一的 key,或是利用数组统计每个字母的数量,将数组作为每组的 key。

解法 1:字符排序生成 key

互为异位词的字符串,字符排序后得到的字符序列完全一致。注意前文提到过list 不可哈希,需转为元组(tuple)作为字典 key。

class Solution1(object): def groupAnagrams(self, strs): """ :type strs: List[str] :rtype: List[List[str]] """ dic = {} for s in strs: key = tuple(sorted(s)) if key not in dic: dic[key] = [] dic[key].append(s) return list(dic.values())

这种写法时间复杂度是 O(nklog k),n 为字符串总数,k 为单字符串最大长度,排序耗时 klog k;空间复杂度是 O(nk),存储全部字符串与哈希键。由于长字符串排序存在一定的性能损耗,所以我们考虑用字母计数法优化。

解法 2:字母计数生成 key

小写字母仅 26 个,我们统计每个字符串中 a-z 出现次数,用长度 26 的计数元组作为 key。由于异位词的字母频率分布完全相同,无需进行排序操作,这样就能避免 klog k 的时间复杂度开销。

class Solution2(object): def groupAnagrams(self, strs): """ :type strs: List[str] :rtype: List[List[str]] """ dic = {} for s in strs: cnt = [0]*26 for c in s: idx = ord(c) - ord('a') cnt[idx]+=1 key = tuple(cnt) if key not in dic: dic[key] = [] dic[key].append(s) return list(dic.values())

这种算法仅遍历每个字符串的全部字符,消除了排序,时间复杂度降到 O(nk)。

本题中我们用到了排序映射、特征计数映射方法,这些都是字符串哈希分组通用模板。

3.最长连续序列

题目给的 nums 数组具有无序性、重复性,暴力遍历会大量重复计算,而如果对数组进行排序再统计,时间复杂度将达到 O(nlog n),不符合题目要求。这时候我们就可以使用set 哈希集合对数组去重,同时实现平均 O(1) 的查询。

考虑好数据结构后算法思想其实很简单:我们仅从连续段起点开始统计长度,如果 x-1 不在集合中,说明 x 是一段连续数字的开头(最短长度为 1,即 x 本身),从起点向后循环查找 x+1、x+2……,再统计当前段长度,更新全局最大值。

class Solution(object): def longestConsecutive(self, nums): num_set = set(nums) max_len = 0 for x in num_set: if x - 1 not in num_set: cur, length = x, 1 while cur + 1 in num_set: cur += 1 length += 1 max_len = max(max_len, length) return max_len

这种算法的时间复杂度是 O(n),每个元素仅参与一次内层循环查询,哈希查询是常数时间。空间复杂度是 O(n),哈希集合存储全部去重数字。

通过本题我们了解了哈希容器 set 的核心用途:去重和O (1) 快速判存。

三、总结

经过三道基础例题训练,我们对于哈希表、可哈希类型、哈希容器有了更深的理解。哈希表基于哈希函数映射,将原本列表遍历查找的 O(n) 时间复杂度,压缩至平均 O(1)。其本质就是额外开辟内存存储映射 / 元素,用存储空间换取查询效率

在处理类似数组或字符串相关问题时,我们首先需要明确需求:若需保存关联关系实现反向查找(如值-下标、特征-分组),则使用字典(dict);若只需去重或判断元素是否存在,则使用集合(set)。构造哈希键时,若需要分组操作,可将同类数据的共同特征作为键。

关于哈希表的底层细节和更多进阶例题,读者们可以自行进一步拓展延申。

http://www.jsqmd.com/news/1221279/

相关文章:

  • 群晖NAS USB网卡驱动终极指南:轻松实现2.5Gbps高速网络升级
  • 零代码RPA自动化:taskt让重复工作一键完成的终极指南
  • 如何在Windows上优雅地管理Android应用?APK Installer场景化使用指南
  • Langchain RAG开发
  • 武汉助产学校 2026 年报考全指南招生简章 - 升学择校早知道
  • js实战中如何排除代码中的bug错误
  • 2026年度第二批襄阳市工程技术职称水平能力测试报名开始
  • 亨得利香港2026年7月官方售後公告:全國網點地址、售後熱線權威發佈 - 亨得利官方
  • 本地大模型部署实战:Ollama与开源模型生态全指南
  • MCP完全实战指南:基于Python搭建本地系统监控服务,让Claude/Cursor实时读取本机CPU/内存/磁盘(完整可运行代码)
  • 基于 RDMA-CM(librdmacm)的聊天程序
  • 鸿蒙 PC Markdown 编辑器错误处理:让失败可恢复而不是只弹提示
  • 2026亲测有效教程:图片大小必须小于300K怎么弄 - 效率工具研究所
  • 2026天津高复学校TOP10出炉,哪所是你的“逆袭摇篮”? - 资讯速览
  • LangGraph[3] ---- 实战 LangGraph Agent:从工具调用到流式交付
  • ADI AD8671, AD8672, AD8674国产替代——士模CM4401, CM4402, CM4404,单通道/双通道/四通道双极性精密低噪声放大器
  • 分享一套锋哥原创的基于Python的高校大学生迎新(新生报到)管理系统(FastAPI+Vue3)
  • 亲身到店探访郑州雷达官方售后服务中心|服务热线及门店官方地址(2026年7月最新) - 亨得利官方服务中心
  • 双视觉系统深度解析:Guizang Social Card Skill如何用电子杂志风与瑞士国际主义设计打造吸睛内容
  • Relative Rotation Graph(相对旋转图)|Highcharts创建RRG 图示列代码
  • 《数字人文技术及运用》课程有感:且教且学且珍惜
  • 会议录音转文字,我为什么放弃了云端方案——智转AI离线语音转写使用体验
  • ChatLaw终极指南:如何快速部署中文法律AI助手并掌握核心功能
  • Apache Commons Collections BidiMap双向映射:实现键值双向查找的完整教程
  • 武汉市科创职业技术学校校企定向就业班招生简章 - 升学择校早知道
  • 企业招投标知识库应该包含哪些资料?从产品功能到中标复盘的完整清单
  • CSYuTuiMian2024隐藏功能:如何快速定位目标院校计算机专业招生动态?
  • 2026泰州黄金回收正规商家:街坊邻居相互介绍的靠谱回收点 - 商业快讯早知道
  • kableExtra新手教程:如何为学术论文创建符合期刊要求的表格
  • 2026毓典奢品汇天津黄金回收指南:高价安全变现,一站式靠谱渠道深度解析 - 二奢行情速报