Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitf85734b

Browse files
authored
Create ACM金牌选手讲解LeetCode算法《哈希》.md
1 parent55b3b6a commitf85734b

File tree

1 file changed

+328
-0
lines changed

1 file changed

+328
-0
lines changed
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
#ACM金牌选手讲解LeetCode算法《哈希表》
2+
3+
大家好,我是编程熊。
4+
5+
往期文章介绍了《线性表》中的数组、链表、栈、队列,以及单调栈和滑动窗口。
6+
7+
[ACM金牌选手讲解LeetCode算法《线性表》](https://mp.weixin.qq.com/s/qwaYOFIksFVqZtA_nisl6g)
8+
9+
[ACM金牌选手讲解LeetCode算法《栈和队列的高级应用》](https://mp.weixin.qq.com/s/I3DQOUmABmWav4nrAiI3Fg)
10+
11+
本期我们学习哈希,其主要作用是加速我们查找数据的速度。
12+
13+
文章将从以下几个方面展开,内容通俗易懂。
14+
15+
若不想了解哈希原理,直接使用哈希表刷题的话,可以直接下拉到"常见的哈希结构"部分。
16+
17+
![哈希](https://gitee.com/hicodebear/upic/raw/master/uPic/image-20210806231308254.png)
18+
19+
20+
21+
##哈希概述
22+
23+
哈希表又称散列表,表现形式为将任意长度的输入,通过哈希算法变成固定长度的输出,哈希表是一种使用空间换取时间的数据结构。
24+
25+
通常是存储`<key,value>`键值对,假设没有哈希表,将`<key,value>`键值对存储在数组中,给定`key`查找的对应的`value`的时间复杂度为`O(n)`
26+
27+
数组就是常见的哈希表,下标就是`key`,对应存储的值就是`value`
28+
29+
通过引如哈希表,将任意长度的输入`key`转化为哈希表中的下标,将`<key,value>`键值对映射到哈希表中,进而加速给定给定`key`,查找`value`的速度,时间复杂度降低到`O(1)`
30+
31+
下图 以两个键值对`<key1,value1>``<key2,value2>`为例,演示了哈希函数和哈希表之间的关系,以及在哈希中起到的作用。
32+
33+
![哈希](https://gitee.com/hicodebear/upic/raw/master/uPic/image-20210730161608774.png"哈希-编程熊")
34+
35+
36+
37+
38+
39+
##哈希表基本操作
40+
41+
###插入
42+
43+
将键值对`<key,value>`插入到哈希表中。
44+
45+
###更新
46+
47+
若哈希表中已存在键值为`key`的键值对,更新哈希表键值对`<key,value>`
48+
49+
###删除
50+
51+
将键值对`<key,value>`从哈希表中删除。
52+
53+
###查询
54+
55+
给定`key`,有两种查询方式。
56+
57+
1. 查找`key` 是否存在于哈希表中。
58+
2. 查找`key`对应的`value`
59+
60+
61+
62+
##哈希函数
63+
64+
哈希函数又称散列函数,即将给定的任意长度的输入值转化为数组的索引(下标)。
65+
66+
如果有一个长度为`n`的数组,其可以存储`n`对键值对,对应的下标为`[0,n-1]`,通常数组的长度是大于等于键值对的数量。
67+
68+
因此我们需要一个哈希函数,将任意长度的输入映射到`[0,n-1]`,并且每个不同的`key`对应的数组下标一定是不一样的,即每个数组下标唯一对应一个`key`
69+
70+
下图以三对`<key,value>`为例,演示了哈希函数`hash`将原始`key`,映射到数组下标的过程,具体哈希函数实现可以有很多方法,感兴趣的读者可以自行探究。
71+
72+
![](https://gitee.com/hicodebear/upic/raw/master/uPic/image-20210806223258109.png)
73+
74+
75+
76+
##哈希冲突
77+
78+
哈希冲突的出现源于哈希函数对两个不同的键`key1``key2``(key1≠key2)`,但经过哈希函数,`hash(key1)=hash(key2)`,将两个不同的`key`,映射到了同一个数组下标位置,导致了哈希冲突。
79+
80+
下图以`key1="abc"``key2="bcd"`,两个不同的`key`,经过哈希函数,映射到同一个数组下标`X`
81+
82+
![哈希冲突](https://gitee.com/hicodebear/upic/raw/master/uPic/image-20210730212532373.png"哈希-编程熊")
83+
84+
###解决哈希冲突的方法
85+
86+
####拉链法
87+
88+
`hash`值相同的`key`放到一个链表中,查找时从前往后遍历链表,找到想要查找的`key`即可。
89+
90+
设需要插入哈希表的数组`a`长度为`n`,哈希表数组长度为`m`,则拉链法查找任意一个`key`的期望时间复杂度为`O(1+n/m)`
91+
92+
下图展示了需要插入哈希表的数组`a`,哈希函数`h(x)`,使用拉链法解决哈希冲突的例子。
93+
94+
![拉链法](https://gitee.com/hicodebear/upic/raw/master/uPic/image-20210730173002905.png"哈希-编程熊")
95+
96+
97+
98+
####开放地址法
99+
100+
从发生冲突的位置起,按照某种规则找到哈希表中其他空闲的位置,将冲突的元素放入这个空闲的位置。
101+
102+
可以找到空闲位置的条件是: 哈希表的长度一定要大于存放元素的个数。
103+
104+
发生冲突后,以什么样的”规则“找到空闲的位置,有很多种方法:
105+
106+
- 线行探查法: 从冲突的位置开始,依次判断下一个位置是否空闲,直至找到空闲位置。
107+
- 平方探查法: 从冲突的位置x开始,第一次增加`1^2`个位置,第二次增加`2^2`...,直至找到空闲的位置。
108+
- 双散列函数探查法等等
109+
110+
####再哈希法
111+
112+
构造多个哈希函数,发生冲突时,更换哈希函数,直至找到空闲位置。
113+
114+
####建立公共溢出区
115+
116+
建立公共溢出区,在哈希表中发生哈希冲突时,将数据存储到公共溢出区。
117+
118+
119+
120+
##常见的哈希结构
121+
122+
当解决问题需要快速查找一个元素/键值对,就可以考虑利用哈希表加速查找的速度。
123+
124+
C++中常用的哈希结构有以下三个:
125+
126+
- 数组
127+
- unordered_set(集合)
128+
- unordered_map(映射: 键值对)
129+
130+
| 种类| 底层实现| Key是否有序| Key是否可以重复| Key是否可以修改| 增删查效率|
131+
| :-------------------------------| :-------| :----------| :--------------| :----------| -----------|
132+
| std::unordered_set(集合)| 哈希表| Key无序| Key不可重复| Key不可修改| O(1)|
133+
| std::unordered_map(映射: 键值对)| 哈希表| Key无序| Key不可重复| Key不可修改| O(1)|
134+
135+
C++标准库中的set、map底层基于红黑树,将会在后续章节中详细介绍。
136+
137+
###std::unordered_set用法
138+
139+
下面介绍常见的用法,一般可以满足刷题需要,详细见`https://zh.cppreference.com/w/cpp/container/unordered_set`
140+
141+
```c++
142+
// 定义一个std::unordered_set
143+
std::unordered_set q;
144+
145+
// 迭代器
146+
// begin: 返回指向起始的迭代器
147+
auto iter = q.begin();
148+
// end: 返回指向末尾的迭代器
149+
auto iter = q.end();
150+
151+
// 容量
152+
// empty: 检查容器是否为空
153+
bool is_empty = q.empty();
154+
// size: 返回容纳的元素数量
155+
int s = q.size();
156+
157+
// 修改器
158+
// clear: 清除内容
159+
q.clear();
160+
// insert: 插入元素或结点
161+
q.insert(key);
162+
// erase: 擦除元素
163+
q.erase(key);
164+
165+
// 查找
166+
// count: 返回匹配特定键的元素数量
167+
int num = q.count(key);
168+
// find: 寻找带有特定键的元素
169+
auto iter = q.find(key);
170+
// contains: 检查容器是否含有带特定键的元素
171+
bool is_contains = q.contains(key);
172+
```
173+
174+
###std::unordered_map用法
175+
176+
下面介绍常见的用法,一般可以满足刷题需要,详细见`https://zh.cppreference.com/w/cpp/container/unordered_map`
177+
178+
```c++
179+
// 定义一个std::unordered_map
180+
std::unordered_map q;
181+
182+
// 迭代器
183+
// begin: 返回指向起始的迭代器
184+
auto iter = q.begin();
185+
// end: 返回指向末尾的迭代器
186+
auto iter = q.end();
187+
188+
// 容量
189+
// empty: 检查容器是否为空
190+
bool is_empty = q.empty();
191+
// size: 返回容纳的元素数量
192+
int s = q.size();
193+
194+
// 修改器
195+
// clear: 清除内容
196+
q.clear();
197+
// insert: 插入元素或结点
198+
q.insert(key);
199+
// erase: 擦除元素
200+
q.erase(key);
201+
202+
// 查找
203+
// count: 返回匹配特定键的元素数量
204+
int num = q.count(key);
205+
// find: 寻找带有特定键的元素
206+
auto iter = q.find(key);
207+
// contains: 检查容器是否含有带特定键的元素
208+
bool is_contains = q.contains(key);
209+
```
210+
211+
##例题
212+
213+
###LeetCode 1. 两数之和
214+
215+
####题意
216+
217+
给定一个整数数组`nums` 和一个整数目标值`target`,请你在该数组中找出**和为目标值***`target`* 的那**两个** 整数,并返回它们的数组下标。
218+
219+
####示例
220+
221+
```
222+
输入:nums = [2,7,11,15], target = 9
223+
输出:[0,1]
224+
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
225+
```
226+
227+
下图以示例演示一下哈希表,将数组插入到哈希表中,查找给定的`key`,即可以在`O(1)` 的时间复杂度查找到,图中`a,b,c,d`指代哈希表的下标。
228+
229+
![示例](https://gitee.com/hicodebear/upic/raw/master/uPic/image-20210730222257466.png"哈希-编程熊")
230+
231+
####题解
232+
233+
建立哈希表,key等于数组的值,value等于值所对应的下标。
234+
235+
然后遍历数组,每次遍历到位置`i`时,检查`target-num[i]` 是否存在,注意`target-num[i]`的位置不能等于`i`
236+
237+
####代码
238+
239+
```java
240+
classSolution {
241+
publicint[]twoSum(int[]nums,inttarget) {
242+
HashMap<Integer,Integer> numExist=newHashMap<Integer,Integer>();
243+
for (int i=0; i< nums.length;++i) {
244+
if (numExist.containsKey(target- nums[i])) {
245+
returnnewint[]{i, numExist.get(target- nums[i])};
246+
}
247+
numExist.put(nums[i], i);
248+
}
249+
returnnewint[2];
250+
}
251+
}
252+
```
253+
254+
255+
256+
###LeetCode 128. 最长连续序列
257+
258+
####题意
259+
260+
给定一个未排序的整数数组`nums` ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
261+
262+
####示例
263+
264+
```
265+
输入:nums = [100,4,200,1,3,2]
266+
输出:4
267+
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
268+
```
269+
270+
####题解
271+
272+
**方法一**
273+
274+
对数组数字排序,然后遍历排序后的数组,找到最长的连续序列。
275+
276+
时间复杂度`O(nlogn)`
277+
278+
**方法二**
279+
280+
哈希可以快速查找一个数字。
281+
282+
将数组数字插入到哈希表,每次随便拿出一个,删除其连续的数字,直至找不到连续的,记录删除的长度,可以找到最长连续序列。
283+
284+
下图以示例展示,如何利用哈希表,找到最长连续序列。
285+
286+
![示例](https://gitee.com/hicodebear/upic/raw/master/uPic/image-20210730233045307.png"哈希-编程熊")
287+
288+
####代码
289+
290+
```c++
291+
classSolution {
292+
public:
293+
int longestConsecutive(vector<int>& nums) {
294+
unordered_set<int> q;
295+
for (int i = 0; i < nums.size(); i++) {
296+
q.insert(nums[i]);
297+
}
298+
int ans = 0;
299+
while (!q.empty()) {
300+
int now =*q.begin();
301+
q.erase(now);
302+
int l = now - 1, r = now + 1;
303+
while (q.find(l) != q.end()) {
304+
q.erase(l);
305+
l--;
306+
}
307+
while(q.find(r) != q.end()) {
308+
q.erase(r);
309+
r++;
310+
}
311+
l = l + 1, r = r - 1;
312+
ans = max(ans, r - l + 1);
313+
}
314+
return ans;
315+
}
316+
};
317+
```
318+
319+
320+
321+
### 习题推荐
322+
323+
1. LeetCode 217. 存在重复元素
324+
2. LeetCode 594. 最长和谐子序列
325+
3. LeetCode 149. 直线上最多的点数
326+
4. LeetCode 332. 重新安排行程
327+
328+
![关注宣传图](https://gitee.com/hicodebear/upic/raw/master/uPic/%E5%85%B3%E6%B3%A8%E5%AE%A3%E4%BC%A0%E5%9B%BE.png)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp