Leetcode100题逐题详解

1. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

你可以按任意顺序返回答案。

示例 1:

复制代码
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

复制代码
输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

复制代码
输入:nums = [3,3], target = 6
输出:[0,1]

提示:

  • 2 <= nums.length <= 104
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109
  • 只会存在一个有效答案

进阶: 你可以想出一个时间复杂度小于 O(n2) 的算法吗?

从零开始详解哈希表(Hash Table)

哈希表(也叫散列表 )是编程中最常用的高效数据结构之一,核心作用是通过「键(Key)」直接快速访问「值(Value)」 ,能把查找、插入、删除的平均时间复杂度做到O(1)(常数时间)。我们从零开始,用最通俗的例子和步骤拆解哈希表的所有核心知识点。

一、先思考:为什么需要哈希表?

在哈希表出现之前,我们常用数组链表存储数据,但它们的查找效率有明显短板:

  1. 数组:如果知道下标,访问是 O (1);但如果要找「某个值对应的下标」(比如找数组中值为 90 的元素位置),需要遍历数组,时间复杂度 O (n)。而且数组的下标是连续整数,若要存储「学号 1001、1005、1010 对应的成绩」,直接用学号当下标会浪费大量空间(下标 0~1000 大部分空着)。
  2. 链表:查找任意元素需要从头遍历,时间复杂度 O (n),效率更低。

我们需要一种数据结构:既能像数组一样快速访问,又能灵活映射非连续的键(比如学号、字符串)到存储位置------ 哈希表就是为解决这个问题而生的。

二、哈希表的核心思想:「键→哈希值→存储位置」的映射

哈希表的本质是 **「数组 + 映射规则」,核心是把 任意类型的键(Key)通过一个函数转换成数组的索引(下标)**,然后把「键值对」存在这个索引位置。

举个生活中的例子:

  • 假设你有 10 个抽屉(对应数组,长度为 10),要存放同学们的成绩单(键 = 学号,值 = 成绩)。
  • 你定了一个规则:学号的最后一位数字就是抽屉编号(这就是「映射规则」,即哈希函数)。
  • 比如学号1001→最后一位 1→放在抽屉 1;学号1005→最后一位 5→放在抽屉 5;学号1010→最后一位 0→放在抽屉 0。
  • 当你想查1001的成绩时,直接看抽屉 1 就行,不用翻所有抽屉 ------ 这就是哈希表的高效性。

三、哈希表的核心组件 1:哈希函数(Hash Function)

上面的「取学号最后一位」就是哈希函数,它是哈希表的灵魂。

1. 哈希函数的定义

哈希函数是一个数学函数,接收任意类型的键(Key) ,输出一个固定范围的整数(哈希值 / 散列值),这个整数会作为数组的索引。

2. 哈希函数的三大要求(设计准则)

一个好的哈希函数必须满足以下条件,否则哈希表的效率会大打折扣:

要求 解释
确定性 同一个键,每次通过哈希函数计算的哈希值必须相同(比如学号 1001 永远对应 1)。
均匀性 哈希值要均匀分布在数组的索引范围内,避免集中在某个区域(减少冲突)。
高效性 计算哈希值的过程要快,时间复杂度 O (1)(不能是复杂的循环 / 递归)。
3. 常见的哈希函数(入门级)

实际开发中哈希函数的实现很复杂,但入门只需了解最常用的几种:

  • 取余法 (最常用):h(key) = key % 数组长度(比如 key 是学号 1001,数组长度 10,h (key)=1)。适用于键是整数的场景。
  • 直接定址法h(key) = a * key + b(线性映射)。适用于键的范围比较连续的场景(比如键是 1~100 的整数)。
  • 字符串哈希 :把字符串转换成整数再取余(比如"abc"a的ASCII码*26² + b的ASCII码*26 + c的ASCII码,再取余)。

四、哈希表的核心问题:哈希冲突(Collision)

1. 什么是哈希冲突?

两个不同的键 通过哈希函数计算出相同的哈希值(即对应数组的同一个索引),就发生了哈希冲突。

比如:学号10011011,用「取最后一位」的哈希函数,都得到 1→两个学号要放在同一个抽屉里,这就是冲突。

注意 :哈希冲突是无法避免的 (比如数组长度是 10,却有 11 个不同的学号,必然有冲突),我们能做的是解决冲突

2. 哈希冲突的两大解决方法(主流)
方法 1:拉链法(链地址法)------Python 字典 / Java HashMap 的核心实现

这是最常用的冲突解决方法,原理是:数组的每个索引位置,不是存储单个键值对,而是存储一个链表(或红黑树);当冲突发生时,把新的键值对添加到链表中

还是用抽屉的例子:

  • 抽屉 1 原本放了学号 1001 的成绩单,现在来了 1011 的成绩单,就在抽屉 1 里挂一个 "小袋子"(链表),把两个成绩单都放进袋子里。
  • 当查找 1011 的成绩时,先通过哈希函数找到抽屉 1,再遍历抽屉 1 里的链表,找到 1011 对应的成绩。

拉链法的优化 :当链表的长度超过一定阈值(比如 8),会把链表转换成红黑树(一种平衡二叉树),把查找时间从 O (k)(k 是链表长度)降到 O (logk),进一步提升效率。

方法 2:开放寻址法 ------Redis 的哈希表用到的方法

原理是:当某个索引位置被占用时,按照某种规则「探测」下一个空的索引位置,把键值对存进去。常见的探测规则有:

  • 线性探测:冲突时,依次查看下一个索引(index+1→index+2→...),直到找到空位置。
  • 二次探测:冲突时,查看 index+1²、index-1²、index+2²、index-2²... 的位置(避免线性探测的 "扎堆" 问题)。

开放寻址法的缺点是:数组满了之后需要扩容,而且删除元素时要标记为 "已删除"(不能直接清空,否则会破坏探测路径);优点是不需要额外的链表空间,内存利用率更高。

五、哈希表的完整存储流程(以拉链法为例)

我们用「存储学号 - 成绩」的例子,梳理哈希表的存储步骤:

  1. 初始化:创建一个长度为 10 的数组(哈希表的底层数组,也叫「桶(Bucket)」),每个位置初始化为空链表。
  2. 存储键值对(1001,90)
    • 计算哈希值:h(1001) = 1001 % 10 = 1
    • 数组索引 1 的位置是空链表,直接把(1001,90)加入链表。
  3. 存储键值对(1011,85)
    • 计算哈希值:h(1011) = 1011 % 10 = 1(冲突)。
    • 把(1011,85)添加到数组索引 1 的链表末尾。
  4. 存储键值对(1005,95)
    • 计算哈希值:h(1005) = 1005 % 10 = 5
    • 数组索引 5 的位置是空链表,直接加入。

六、哈希表的时间复杂度

哈希表的效率取决于哈希函数的均匀性冲突的多少

  • 平均情况 :查找、插入、删除的时间复杂度都是O(1)(直接通过哈希值找到索引,链表长度很短甚至只有一个元素)。
  • 最坏情况 :如果所有键都哈希到同一个索引(比如哈希函数设计得很差,所有 key%10 都等于 1),链表会变成一个长链,此时时间复杂度退化为O(n)(需要遍历链表)。

如何避免最坏情况?

  1. 设计均匀的哈希函数。
  2. 当哈希表的负载因子 超过阈值时,进行扩容
python 复制代码
from typing import List

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        num_map = {}  # 定义哈希表(字典),存储{数字: 索引}
        for index, num in enumerate(nums):  # 遍历数组,O(n)
            complement = target - num  # 计算补数
            if complement in num_map:  # 查找补数:O(1)(平均)
                return [num_map[complement], index]  # 返回结果
            num_map[num] = index  # 插入当前数字和索引:O(1)(平均)
        return []
相关推荐
whitelbwwww2 小时前
Pytorch--张量
开发语言·pytorch·python
小毅&Nora2 小时前
【后端】【工具】短信短链接如何做到“永不丢失“?从哈希冲突到百万QPS的可靠性设计
算法·哈希算法
2301_764441332 小时前
基于python与Streamlit构建的卫星数据多维可视化分析
开发语言·python·信息可视化
陈奕昆2 小时前
n8n实战营Day3课时3:库存物流联动·全流程测试与异常调试
人工智能·python·n8n
weixin_457760002 小时前
DefaultCPUAllocator: can‘t allocate memory
python·神经网络
Acc1oFl4g2 小时前
Java安全之SpEL表达式注入入门学习
java·学习·安全
珂朵莉MM2 小时前
第七届全球校园人工智能算法精英大赛-算法巅峰赛产业命题赛第3赛季优化题--碳中和
人工智能·算法
测试人社区-小明2 小时前
测试金字塔的演进:如何构建健康的自动化测试套件
python·测试工具·数据挖掘·pycharm·机器人·github·量子计算