ACM金牌选手讲解LeetCode算法《哈希》
大家好,我是编程熊。
往期文章介绍了《线性表》中的数组、链表、栈、队列,以及单调栈和滑动窗口。
ACM金牌选手讲解LeetCode算法《栈和队列的高级应用》
本期我们学习哈希,其主要作用是加速我们查找数据的速度。
文章将从以下几个方面展开,内容通俗易懂。
若不想了解哈希原理,直接使用哈希表刷题的话,可以直接下拉到"常见的哈希结构"部分。
哈希概述
哈希表又称散列表,表现形式为将任意长度的输入,通过哈希算法变成固定长度的输出,哈希表是一种使用空间换取时间的数据结构。
通常是存储 <key,value>
键值对,假设没有哈希表,将 <key,value>
键值对存储在数组中,给定key
查找的对应的value
的时间复杂度为O(n)
;
数组就是常见的哈希表,下标就是key
,对应存储的值就是value
。
通过引如哈希表,将任意长度的输入key
转化为哈希表中的下标,将<key,value>
键值对映射到哈希表中,进而加速给定给定key
,查找value
的速度,时间复杂度降低到O(1)
。
下图 以两个键值对<key1,value1>
、<key2,value2>
为例,演示了哈希函数和哈希表之间的关系,以及在哈希中起到的作用。
哈希表基本操作
插入
将键值对<key,value>
插入到哈希表中。
更新
若哈希表中已存在键值为key
的键值对,更新哈希表键值对<key,value>
。
删除
将键值对<key,value>
从哈希表中删除。
查询
给定key
,有两种查询方式。
- 查找
key
是否存在于哈希表中。 - 查找
key
对应的value
。
哈希函数
哈希函数又称散列函数,即将给定的任意长度的输入值转化为数组的索引(下标)。
如果有一个长度为n
的数组,其可以存储n
对键值对,对应的下标为[0,n-1]
,通常数组的长度是大于等于键值对的数量。
因此我们需要一个哈希函数,将任意长度的输入映射到[0,n-1]
,并且每个不同的key
对应的数组下标一定是不一样的,即每个数组下标唯一对应一个key
。
下图以三对<key,value>
为例,演示了哈希函数hash
将原始key
,映射到数组下标的过程,具体哈希函数实现可以有很多方法,感兴趣的读者可以自行探究。
哈希冲突
哈希冲突的出现源于哈希函数对两个不同的键key1
、key2
(key1≠key2)
,但经过哈希函数,hash(key1)=hash(key2)
,将两个不同的key
,映射到了同一个数组下标位置,导致了哈希冲突。
下图以key1="abc"
,key2="bcd"
,两个不同的key
,经过哈希函数,映射到同一个数组下标X
。
解决哈希冲突的方法
拉链法
将hash
值相同的key
放到一个链表中,查找时从前往后遍历链表,找到想要查找的key
即可。
设需要插入哈希表的数组a
长度为n
,哈希表数组长度为m
,则拉链法查找任意一个key
的期望时间复杂度为O(1+n/m)
。
下图展示了需要插入哈希表的数组a
,哈希函数h(x)
,使用拉链法解决哈希冲突的例子。
开放地址法
从发生冲突的位置起,按照某种规则找到哈希表中其他空闲的位置,将冲突的元素放入这个空闲的位置。
可以找到空闲位置的条件是: 哈希表的长度一定要大于存放元素的个数。
发生冲突后,以什么样的”规则“找到空闲的位置,有很多种方法:
- 线行探查法: 从冲突的位置开始,依次判断下一个位置是否空闲,直至找到空闲位置。
- 平方探查法: 从冲突的位置x开始,第一次增加
1^2
个位置,第二次增加2^2
...,直至找到空闲的位置。 - 双散列函数探查法等等
再哈希法
构造多个哈希函数,发生冲突时,更换哈希函数,直至找到空闲位置。
建立公共溢出区
建立公共溢出区,在哈希表中发生哈希冲突时,将数据存储到公共溢出区。
常见的哈希结构
当解决问题需要快速查找一个元素/键值对,就可以考虑利用哈希表加速查找的速度。
C++中常用的哈希结构有以下三个:
- 数组
- unordered_set(集合)
- unordered_map(映射: 键值对)
种类 | 底层实现 | Key是否有序 | Key是否可以重复 | Key是否可以修改 | 增删查效率 |
---|---|---|---|---|---|
std::unordered_set(集合) | 哈希表 | Key无序 | Key不可重复 | Key不可修改 | O(1) |
std::unordered_map(映射: 键值对) | 哈希表 | Key无序 | Key不可重复 | Key不可修改 | O(1) |
C++标准库中的set、map底层基于红黑树,将会在后续章节中详细介绍。
std::unordered_set用法
下面介绍常见的用法,一般可以满足刷题需要,详细见https://zh.cppreference.com/w/cpp/container/unordered_set
。
// 定义一个std::unordered_set
std::unordered_set q;
// 迭代器
// begin: 返回指向起始的迭代器
auto iter = q.begin();
// end: 返回指向末尾的迭代器
auto iter = q.end();
// 容量
// empty: 检查容器是否为空
bool is_empty = q.empty();
// size: 返回容纳的元素数量
int s = q.size();
// 修改器
// clear: 清除内容
q.clear();
// insert: 插入元素或结点
q.insert(key);
// erase: 擦除元素
q.erase(key);
// 查找
// count: 返回匹配特定键的元素数量
int num = q.count(key);
// find: 寻找带有特定键的元素
auto iter = q.find(key);
// contains: 检查容器是否含有带特定键的元素
bool is_contains = q.contains(key);
std::unordered_map用法
下面介绍常见的用法,一般可以满足刷题需要,详细见https://zh.cppreference.com/w/cpp/container/unordered_map
。
// 定义一个std::unordered_map
std::unordered_map q;
// 迭代器
// begin: 返回指向起始的迭代器
auto iter = q.begin();
// end: 返回指向末尾的迭代器
auto iter = q.end();
// 容量
// empty: 检查容器是否为空
bool is_empty = q.empty();
// size: 返回容纳的元素数量
int s = q.size();
// 修改器
// clear: 清除内容
q.clear();
// insert: 插入元素或结点
q.insert(key);
// erase: 擦除元素
q.erase(key);
// 查找
// count: 返回匹配特定键的元素数量
int num = q.count(key);
// find: 寻找带有特定键的元素
auto iter = q.find(key);
// contains: 检查容器是否含有带特定键的元素
bool is_contains = q.contains(key);
例题
LeetCode 1. 两数之和
题意
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
示例
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
下图以示例演示一下哈希表,将数组插入到哈希表中,查找给定的key
,即可以在O(1)
的时间复杂度查找到,图中a,b,c,d
指代哈希表的下标。
题解
建立哈希表,key等于数组的值,value等于值所对应的下标。
然后遍历数组,每次遍历到位置i
时,检查 target-num[i]
是否存在,注意target-num[i]
的位置不能等于i
。
代码
class Solution {
public int[] twoSum(int[] nums, int target) {
HashMap<Integer, Integer> numExist = new HashMap<Integer, Integer>();
for (int i = 0; i < nums.length; ++i) {
if (numExist.containsKey(target - nums[i])) {
return new int[]{i, numExist.get(target - nums[i])};
}
numExist.put(nums[i], i);
}
return new int[2];
}
}
LeetCode 128. 最长连续序列
题意
给定一个未排序的整数数组 nums
,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
示例
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
题解
方法一
对数组数字排序,然后遍历排序后的数组,找到最长的连续序列。
时间复杂度O(nlogn)
方法二
哈希可以快速查找一个数字。
将数组数字插入到哈希表,每次随便拿出一个,删除其连续的数字,直至找不到连续的,记录删除的长度,可以找到最长连续序列。
下图以示例展示,如何利用哈希表,找到最长连续序列。
代码
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
unordered_set<int> q;
for (int i = 0; i < nums.size(); i++) {
q.insert(nums[i]);
}
int ans = 0;
while (!q.empty()) {
int now = *q.begin();
q.erase(now);
int l = now - 1, r = now + 1;
while (q.find(l) != q.end()) {
q.erase(l);
l--;
}
while(q.find(r) != q.end()) {
q.erase(r);
r++;
}
l = l + 1, r = r - 1;
ans = max(ans, r - l + 1);
}
return ans;
}
};
习题推荐
- LeetCode 217. 存在重复元素
- LeetCode 594. 最长和谐子序列
- LeetCode 149. 直线上最多的点数
- LeetCode 332. 重新安排行程
【下面是粉丝福利】
【计算机学习核心资源】: 涵盖了所有计算机学习核心资源,多看看进大厂问题不大。
【github宝藏仓库】: 对学习和面试都非常有帮助,学完超过99%同龄人。
ACM金牌选手讲解LeetCode算法《哈希》的更多相关文章
- ACM金牌选手讲解LeetCode算法《栈和队列的高级应用》
大家好,我是编程熊,双非逆袭选手,字节跳动.旷视科技前员工,ACM金牌,保研985,<ACM金牌选手讲解LeetCode算法系列>作者. 上一篇文章讲解了<线性表>中的数组.链 ...
- ACM金牌选手算法讲解《线性表》
哈喽,大家好,我是编程熊,双非逆袭选手,字节跳动.旷视科技前员工,ACM亚洲区域赛金牌,保研985研究生,分享算法与数据结构.计算机学习经验,帮助大家进大厂~ 公众号:『编程熊』 文章首发于: ACM ...
- ACM金牌选手整理的【LeetCode刷题顺序】
算法和数据结构知识点图 首先,了解算法和数据结构有哪些知识点,在后面的学习中有 大局观,对学习和刷题十分有帮助. 下面是我花了一天时间花的算法和数据结构的知识结构,大家可以看看. 后面是为大家 精心挑 ...
- 编程熊讲解LeetCode算法《二叉树》
大家好,我是编程熊. 往期我们一起学习了<线性表>相关知识. 本期我们一起学习二叉树,二叉树的问题,大多以递归为基础,根据题目的要求,在递归过程中记录关键信息,进而解决问题. 如果还未学习 ...
- LeetCode算法题-Design HashMap(Java实现)
这是悦乐书的第299次更新,第318篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第167题(顺位题号是706).在不使用任何内置哈希表库的情况下设计HashMap.具体 ...
- LeetCode算法题-Design HashSet(Java实现)
这是悦乐书的第298次更新,第317篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第166题(顺位题号是705).不使用任何内建的hash表库设计一个hash集合,应包含 ...
- LeetCode算法题-Relative Ranks(Java实现)
这是悦乐书的第248次更新,第261篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第115题(顺位题号是506).根据N名运动员的得分,找到他们的相对等级和得分最高的三个 ...
- LeetCode算法题-Linked List Cycle(Java实现)
这是悦乐书的第176次更新,第178篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第35题(顺位题号是141).给定一个链表,确定它是否有一个循环. 本次解题使用的开发工 ...
- 小旭讲解 LeetCode 53. Maximum Subarray 动态规划 分治策略
原题 Given an integer array nums, find the contiguous subarray (containing at least one number) which ...
随机推荐
- JMeter定时器种类+详细教程举例
首先,我们先了解一下定时器的常见种类以及它的作用. 原文地址:https://www.cnblogs.com/istart/p/11184533.html 一.定时器种类+作用 上面是我截图的自己有道 ...
- 基于ABP落地领域驱动设计-04.领域服务和应用服务的最佳实践和原则
目录 系列文章 领域服务 应用服务 学习帮助 系列文章 基于ABP落地领域驱动设计-00.目录和前言 基于ABP落地领域驱动设计-01.全景图 基于ABP落地领域驱动设计-02.聚合和聚合根的最佳实践 ...
- vscode 配置 Pug Compile Hero Pro 插件步骤
这个随笔主要介绍 vscode 配置 Pug Compile Hero Pro 插件的步骤,实现快速使用less 以及 scss 等的编程语言 第一步 当然是安装我们的插件啦! 在插件商店里 搜 Sa ...
- kubelet分析-csi driver注册源码分析
kubelet注册csi driver分析 kubelet注册csi driver的相关功能代码与kubelet的pluginManager有关,所以接下来对pluginManager进行分析.分析将 ...
- 14、redis安装及数据类型
14.0.服务器配置: 服务器名称 ip地址 controller-node1 172.16.1.90 14.1.什么是redis: 1.redis的特点: (1)redis是一个开源的使用c语言编写 ...
- Jquery手机点击其他地方隐藏控件问题
因为不太懂mui的底部导航栏的操作,所以自己写了用很普通的方法实现手机底部导航栏,遇到了很多问题.比如:要实现点击底部菜单栏上某一个菜单,显示子菜单,然后点击手机空白处,隐藏菜单. 实现方法是: // ...
- 实例化Class类的5种方法
实例说明 java的数据类型分为两类:基础数据类型和引用数据类型.对于每种类型的对象,java虚拟机会实例化不可变的java.lang.Class对象.它提供了在运行时检查对象属性的方法,这些属性包括 ...
- filebeat 提取获取massage字段 利用pipeline grok 7.12
嘴巴会说(情商)比技术有时候更重要! 水平有限,希望你看完有所收获! 背景 1,filebeat直连Elasticsearch,需要对massage提取一些特定的字段. 2,如果你对数据需要处理的比较 ...
- buu crypto 凯撒?替换?呵呵!
一. 以为是简单的凯撒加密,但是分析Ascill表,发现毫无规律,意味着要爆破出所有可能.只能用在线工具来弄了,脚本是不可能写的(狗头) 找到了,但是提交不成功,需要变成小写,用脚本转换一下,同时很坑 ...
- Spring Cloud中的注解
一.Eureka @EnableEurekaServer: @EnableDiscoverClient:标注服务是一个Eureka的客户端 @LoadBalanced:自动构造LoadBalancer ...