大家好,我是编程熊。

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

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. 学习Qt Charts - Qt Charts的坐标轴

    这次来学学Qt chart 的坐标轴 有这么一组数据: 这是深圳市2019年6月份的天气预报(来自中国天气网:深圳),里面有每天的最高温度,把这最高温度做成个数组,如下: int daily_temp ...

  2. Pytest学习笔记8-参数化

    前言 我们在实际自动化测试中,某些测试用例是无法通过一组测试数据来达到验证效果的,所以需要通过参数化来传递多组数据 在unittest中,我们可以使用第三方库parameterized来对数据进行参数 ...

  3. 安聊服务端Netty的应用

    Netty简介 Netty是一个面向网络编程的Java基础框架,它基于异步的事件驱动,并且内置多种网络协议的支持,可以快速地开发可维护的高性能的面向协议的服务器和客户端. 安聊简介 安聊是一个即时聊天 ...

  4. 第二篇CTF-MISC

    第一篇写成了日记,发布不了.第一篇CTF-MISC 04.坚持60S 附件下载下来,是个jar的文件,打开 耶?这是嘛呀? 反正没看懂,既然是jar文件,直接上jd-gui反编译一波试试 这么明显的f ...

  5. Unity il2cpp加密

    防市面上百分之九十的破解者,下面是Mono改为il2cpp的面板 点击:File > BuildSettings > PlayerSettings > OtherSettings & ...

  6. 43、uniq命令

    相邻去重 uniq -c 表示相邻去重并统计: 1.uniq介绍: uniq是对指定的ascii文件或标准输入进行唯一性检查,以判断文本文件中重复出现的行,常用于系统排查及日志分析: 2.命令格式: ...

  7. hdu 1394 线段树计算逆序数

    线段树计算逆序数的原理: 用线段树来统计已插入的数的个数(所以要保证最大的那个数不能太大,否则数组都开不了),然后每插入一个数,就查询比插入的数大的个数,累加即可. 这个题还有一个特点就是,题目给的是 ...

  8. Maven:Maven的project标签报错红线

    作者在外网完成demo项目,把Maven的本地库打成压缩包放进内网时,Maven的project标签报错红线,且别的依赖不报错,同时Maven不引入本地仓库的依赖包. 解决方法: 进入自己的Maven ...

  9. [心得体会]mysql复习

    1. 进入企业需要注意的事情 (1) 查看测试服和本地的mysql版本是否一致(2) 确认sql_mode是否和线上版本一致 show VARIABLES LIKE 'sql_mode'; (3) m ...

  10. vsftpd配置 (转)

      # # The default compiled in settings are fairly paranoid. This sample file # loosens things up a b ...