大家好,我是编程熊。

往期文章介绍了《线性表》中的数组、链表、栈、队列,以及单调栈和滑动窗口。

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,有两种查询方式。

  1. 查找key 是否存在于哈希表中。
  2. 查找key对应的value

哈希函数

哈希函数又称散列函数,即将给定的任意长度的输入值转化为数组的索引(下标)。

如果有一个长度为n的数组,其可以存储n对键值对,对应的下标为[0,n-1],通常数组的长度是大于等于键值对的数量。

因此我们需要一个哈希函数,将任意长度的输入映射到[0,n-1],并且每个不同的key对应的数组下标一定是不一样的,即每个数组下标唯一对应一个key

下图以三对<key,value>为例,演示了哈希函数hash将原始key,映射到数组下标的过程,具体哈希函数实现可以有很多方法,感兴趣的读者可以自行探究。

哈希冲突

哈希冲突的出现源于哈希函数对两个不同的键key1key2 (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;
}
};

习题推荐

  1. LeetCode 217. 存在重复元素
  2. LeetCode 594. 最长和谐子序列
  3. LeetCode 149. 直线上最多的点数
  4. LeetCode 332. 重新安排行程

【下面是粉丝福利】

【计算机学习核心资源】: 涵盖了所有计算机学习核心资源,多看看进大厂问题不大。

【github宝藏仓库】: 对学习和面试都非常有帮助,学完超过99%同龄人。

ACM金牌选手讲解LeetCode算法《哈希》的更多相关文章

  1. ACM金牌选手讲解LeetCode算法《栈和队列的高级应用》

    大家好,我是编程熊,双非逆袭选手,字节跳动.旷视科技前员工,ACM金牌,保研985,<ACM金牌选手讲解LeetCode算法系列>作者. 上一篇文章讲解了<线性表>中的数组.链 ...

  2. ACM金牌选手算法讲解《线性表》

    哈喽,大家好,我是编程熊,双非逆袭选手,字节跳动.旷视科技前员工,ACM亚洲区域赛金牌,保研985研究生,分享算法与数据结构.计算机学习经验,帮助大家进大厂~ 公众号:『编程熊』 文章首发于: ACM ...

  3. ACM金牌选手整理的【LeetCode刷题顺序】

    算法和数据结构知识点图 首先,了解算法和数据结构有哪些知识点,在后面的学习中有 大局观,对学习和刷题十分有帮助. 下面是我花了一天时间花的算法和数据结构的知识结构,大家可以看看. 后面是为大家 精心挑 ...

  4. 编程熊讲解LeetCode算法《二叉树》

    大家好,我是编程熊. 往期我们一起学习了<线性表>相关知识. 本期我们一起学习二叉树,二叉树的问题,大多以递归为基础,根据题目的要求,在递归过程中记录关键信息,进而解决问题. 如果还未学习 ...

  5. LeetCode算法题-Design HashMap(Java实现)

    这是悦乐书的第299次更新,第318篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第167题(顺位题号是706).在不使用任何内置哈希表库的情况下设计HashMap.具体 ...

  6. LeetCode算法题-Design HashSet(Java实现)

    这是悦乐书的第298次更新,第317篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第166题(顺位题号是705).不使用任何内建的hash表库设计一个hash集合,应包含 ...

  7. LeetCode算法题-Relative Ranks(Java实现)

    这是悦乐书的第248次更新,第261篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第115题(顺位题号是506).根据N名运动员的得分,找到他们的相对等级和得分最高的三个 ...

  8. LeetCode算法题-Linked List Cycle(Java实现)

    这是悦乐书的第176次更新,第178篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第35题(顺位题号是141).给定一个链表,确定它是否有一个循环. 本次解题使用的开发工 ...

  9. 小旭讲解 LeetCode 53. Maximum Subarray 动态规划 分治策略

    原题 Given an integer array nums, find the contiguous subarray (containing at least one number) which ...

随机推荐

  1. php 安装 yii 报错: phpunit/phpunit 4.8.32 requires ext-dom *

    php 安装 yii 报错: phpunit/phpunit 4.8.32 requires ext-dom * 我的版本是7.0,以7.0为例演示. 先装这两个拓展试试: sudo apt-get ...

  2. 在vs中调试关闭之后不关闭页面

    在vs中调试api时会自动打开一个新的浏览器窗口,在关闭这个浏览器窗口时,会关闭调试.关闭调试时也会关闭浏览器窗口. 设置成调试时在已有的浏览器中打开调试页面,关闭调试也不会关掉浏览器窗口,反之亦然 ...

  3. LVM与磁盘配额

    LVM与磁盘配额 目录 一.LVM概述 1.1.LVM 概述 1.2.LVM机制的基本概念 二.LVM 管理命令 2.1.主要命令 2.2.LVM命令详解 三.设置磁盘配额 3.1.磁盘配额的概述 3 ...

  4. 试着给VuePress添加全局禁止爬取支持,基于vuepress-plugin-robots

    背景 有时候,我们有些内部网站希望不被外部抓取,那么我们可以借助vuepress-plugin-robots来生成robots.txt文件,来告诉爬虫不要抓取页面. 安装 npm install vu ...

  5. 堆&&优先队列&&TreeMap

    题目描述 5710. 积压订单中的订单总数 题解 题目不难,主要是要读懂题意,一步步模拟,代码较长,需要细心检查. 坑较多,比如我犯了很多傻逼问题:想都不想就拿1<<9+7当作100000 ...

  6. 资源:Kafka消息队列下载路径

    Kafka下载路径 http://kafka.apache.org/downloads.html

  7. FreeRTOS常用函数

    一.任务 任务创建和删除xTaskCreate                                 任务创建xTaskDelete                              ...

  8. [Kong] key-auth实现对API请求的密钥认证

    目录 1. 配置密钥验证插件 2. 确认插件配置正确 3. 创建cunsumer 4. 给cunsumer提供关键凭证 5. 验证 6. 小结 [前言]: 下面我们将配置key-auth插件以向服务添 ...

  9. Git的安装和配置 -入门

    Git的版本有很多种,适应各种windows,IOS, Linux平台的安装. 我用的是linux Centos7的版本: 1. 安装命令用Yum, 非常简单就可以安装完毕. yum install ...

  10. golang开发:Error的使用

    Error是Go语言开发中最基础也是最重要的部分,跟其他语言的try catch的作用基本一致,想想在PHP JAVA开发中,try catch 不会使用,或者使用不灵活,就无法感知到程序运行中出现了 ...