返回 LeetCode 刷题

Markdown File

拉链法

拉链法.md

1开放定址法感觉不太好用,刷题也不常见,所以我就没学...就看一下拉链法好了
2哈希表:拉链法笔记
3
41. 前提条件
5哈希表本质:
6key
7
8hash函数
9
10数组下标
11
12例如:hash(key) = key % 10
13
14插入:21
15
16得到:21 % 10 = 1
17
18所以放到:table[1]
19
202. 核心概念
21
22如果没有冲突:
23table[1] = 21
24
25如果冲突:
26
2721 % 10 = 1
2831 % 10 = 1
2941 % 10 = 1
30
31都落到下标 1。
32
33拉链法不是往后找空位,而是:
34
35table[1] -> 21 -> 31 -> 41
36
37也就是:数组 + 链表
38
393. 结构理解
40
41数组每个位置叫一个桶:
42
43table[0]
44table[1]
45table[2]
46...
47
48每个桶里放一条链表。
49
50例如:
51
52下标: 0 1 2 3
53 NULL 21->31->41 NULL 13->23
54
55所以查找 31:
56
5731 % 10 = 1
58
59先找到:table[1]
60
61然后在这条链表里找:21 -> 31
62
634. 结构体模板
64
65这里我们存两样东西:
66
67key:数组值
68value:数组下标
69
70因为两数之和要返回原下标。
71
72#include <stdlib.h>
73#include <stdbool.h>
74
75#define HASH_SIZE 10007
76
77struct HashNode {
78 int key;
79 int value;
80 struct HashNode* next;
81};
82
83struct HashTable {
84 struct HashNode* table[HASH_SIZE];
85};
86
875. 哈希函数
88
89C 里面 % 遇到负数可能得到负结果,所以要处理一下。
90
91int hash(int key)
92{
93 int h = key % HASH_SIZE;
94
95 if(h < 0)
96 {
97 h += HASH_SIZE;
98 }
99
100 return h;
101}
102
103例如:
104
105key = -3
106
107先:
108
109-3 % 10007 = -3
110
111修正:
112
113-3 + 10007
114
115变成合法下标。
116
1176. 初始化哈希表
118void initHashTable(struct HashTable* h)
119{
120 for(int i = 0; i < HASH_SIZE; i++)
121 {
122 h->table[i] = NULL;
123 }
124}
125
126意思:
127
128每个桶一开始都是空链表
129
1307. 查找
131struct HashNode* find(struct HashTable* h, int key)
132{
133 int index = hash(key);
134
135 struct HashNode* cur = h->table[index];
136
137 while(cur != NULL)
138 {
139 if(cur->key == key)
140 {
141 return cur;
142 }
143
144 cur = cur->next;
145 }
146
147 return NULL;
148}
149
150理解:
151
1521. 先算桶下标
1532. 找到 table[index]
1543. 在链表里顺着 next 找
1558. 插入
156
157这里用头插法。
158
159void insert(struct HashTable* h, int key, int value)
160{
161 int index = hash(key);
162
163 struct HashNode* node =
164 malloc(sizeof(struct HashNode));
165
166 node->key = key;
167 node->value = value;
168
169 node->next = h->table[index];
170
171 h->table[index] = node;
172}
173
174例如桶里原来:
175
176table[1] -> 21 -> 31
177
178插入 41:
179
180node->next = table[1]
181table[1] = node
182
183结果:
184
185table[1] -> 41 -> 21 -> 31
186
187这就是链表头插法。
188
1899. 释放哈希表
190
191C 里 malloc 了节点,就要 free。
192
193void freeHashTable(struct HashTable* h)
194{
195 for(int i = 0; i < HASH_SIZE; i++)
196 {
197 struct HashNode* cur = h->table[i];
198
199 while(cur != NULL)
200 {
201 struct HashNode* next = cur->next;
202 free(cur);
203 cur = next;
204 }
205
206 h->table[i] = NULL;
207 }
208}
209
21010. 拉链法完整模板
211#include <stdlib.h>
212#include <stdbool.h>
213
214#define HASH_SIZE 10007
215
216struct HashNode {
217 int key;
218 int value;
219 struct HashNode* next;
220};
221
222struct HashTable {
223 struct HashNode* table[HASH_SIZE];
224};
225
226int hash(int key)
227{
228 int h = key % HASH_SIZE;
229
230 if(h < 0)
231 {
232 h += HASH_SIZE;
233 }
234
235 return h;
236}
237
238void initHashTable(struct HashTable* h)
239{
240 for(int i = 0; i < HASH_SIZE; i++)
241 {
242 h->table[i] = NULL;
243 }
244}
245
246struct HashNode* find(struct HashTable* h, int key)
247{
248 int index = hash(key);
249
250 struct HashNode* cur = h->table[index];
251
252 while(cur != NULL)
253 {
254 if(cur->key == key)
255 {
256 return cur;
257 }
258
259 cur = cur->next;
260 }
261
262 return NULL;
263}
264
265void insert(struct HashTable* h, int key, int value)
266{
267 int index = hash(key);
268
269 struct HashNode* node =
270 malloc(sizeof(struct HashNode));
271
272 node->key = key;
273 node->value = value;
274
275 node->next = h->table[index];
276
277 h->table[index] = node;
278}
279
280void freeHashTable(struct HashTable* h)
281{
282 for(int i = 0; i < HASH_SIZE; i++)
283 {
284 struct HashNode* cur = h->table[i];
285
286 while(cur != NULL)
287 {
288 struct HashNode* next = cur->next;
289 free(cur);
290 cur = next;
291 }
292
293 h->table[i] = NULL;
294 }
295}
296
29711. LeetCode 1 两数之和:哈希实现
298核心思想
299
300遍历每个数 nums[i]。
301
302当前数是:x = nums[i]
303
304想要另一个数:need = target - x
305
306先去哈希表查:need 是否出现过
307
308如果出现过,直接返回:
309
310need 的下标
311当前 i
312
313如果没出现过,就把当前数存进去:
314
315key = nums[i]
316value = i
317
31812. 为什么先查再插?
319
320例如:
321
322nums = [3,3]
323target = 6
324
325i = 0:
326
327x = 3
328need = 3
329
330表里没有 3。
331
332插入:
333
3343 -> 0
335
336i = 1:
337
338x = 3
339need = 3
340
341查到之前的:
342
3433 -> 0
344
345返回:
346
347[0,1]
348
349如果先插再查,可能会把自己和自己配对。
350
351所以:
352
353先查 need
354再插 x
35513. 两数之和 C 代码
356#include <stdlib.h>
357#include <stdbool.h>
358
359#define HASH_SIZE 10007
360
361struct HashNode {
362 int key;
363 int value;
364 struct HashNode* next;
365};
366
367struct HashTable {
368 struct HashNode* table[HASH_SIZE];
369};
370
371int hash(int key)
372{
373 int h = key % HASH_SIZE;
374
375 if(h < 0)
376 {
377 h += HASH_SIZE;
378 }
379
380 return h;
381}
382
383void initHashTable(struct HashTable* h)
384{
385 for(int i = 0; i < HASH_SIZE; i++)
386 {
387 h->table[i] = NULL;
388 }
389}
390
391struct HashNode* find(struct HashTable* h, int key)
392{
393 int index = hash(key);
394
395 struct HashNode* cur = h->table[index];
396
397 while(cur != NULL)
398 {
399 if(cur->key == key)
400 {
401 return cur;
402 }
403
404 cur = cur->next;
405 }
406
407 return NULL;
408}
409
410void insert(struct HashTable* h, int key, int value)
411{
412 int index = hash(key);
413
414 struct HashNode* node =
415 malloc(sizeof(struct HashNode));
416
417 node->key = key;
418 node->value = value;
419
420 node->next = h->table[index];
421
422 h->table[index] = node;
423}
424
425void freeHashTable(struct HashTable* h)
426{
427 for(int i = 0; i < HASH_SIZE; i++)
428 {
429 struct HashNode* cur = h->table[i];
430
431 while(cur != NULL)
432 {
433 struct HashNode* next = cur->next;
434 free(cur);
435 cur = next;
436 }
437 }
438}
439
440int* twoSum(int* nums, int numsSize, int target, int* returnSize)
441{
442 struct HashTable h;
443
444 initHashTable(&h);
445
446 int* ans = malloc(2 * sizeof(int));
447
448 *returnSize = 2;
449
450 for(int i = 0; i < numsSize; i++)
451 {
452 int x = nums[i];
453
454 int need = target - x;
455
456 struct HashNode* found = find(&h, need);
457
458 if(found != NULL)
459 {
460 ans[0] = found->value;
461 ans[1] = i;
462
463 freeHashTable(&h);
464
465 return ans;
466 }
467
468 insert(&h, x, i);
469 }
470
471 *returnSize = 0;
472
473 free(ans);
474 freeHashTable(&h);
475
476 return NULL;
477}
478
47914. 例子流程
480nums = [2,7,11,15]
481target = 9
482
483i = 0:
484
485x = 2
486need = 7
487
488查 7:
489
490没找到
491
492插入:
493
4942 -> 0
495
496i = 1:
497
498x = 7
499need = 2
500
501查 2:
502
503找到了,下标 0
504
505返回:
506
507[0,1]
50815. 易错点
5091. key 和 value 不一样
510
511在两数之和里:
512
513key = nums[i]
514value = i
515
516不能只存值,因为答案要返回下标。
517
5182. 负数 hash 要处理
519int h = key % HASH_SIZE;
520
521if(h < 0)
522{
523 h += HASH_SIZE;
524}
5253. 先查再插
526
527避免同一个元素用两次。
528
5294. 冲突用链表解决
530
531同一个桶里可能有多个节点:
532
533table[index] -> node1 -> node2 -> node3
534
535查找时要遍历这条链。
536
537笔记版总结
538拉链法哈希表
539
540--------------------------------
541
542结构:
543
544数组 + 链表
545
546数组每个位置叫桶 bucket
547
548每个桶里是一条链表
549
550--------------------------------
551
552哈希函数:
553
554index = key % HASH_SIZE
555
556--------------------------------
557
558冲突:
559
560多个 key 得到同一个 index
561
562--------------------------------
563
564解决:
565
566把它们挂到同一个桶的链表里
567
568--------------------------------
569
570插入:
571
5721. 计算 index
5732. 创建新节点
5743. 头插到 table[index]
575
576--------------------------------
577
578查找:
579
5801. 计算 index
5812. 遍历 table[index] 链表
5823. 找到 key 返回节点
5834. 找不到返回 NULL
584
585--------------------------------
586
587两数之和:
588
589遍历 nums[i]
590
591x = nums[i]
592
593need = target - x
594
595先查 need 是否出现过
596
597如果出现过:
598 返回 need 的下标和 i
599
600否则:
601 插入 x 和 i
602
603--------------------------------
604
605key = 数值
606value = 下标
607
608一句话:
609
610拉链法就是:数组快速定位桶,链表解决同桶冲突;两数之和就是边查 need,边存当前数。
611看一下py的字典实现,因为内置了字典,而c需要手写底层hash
612
613def twoSum(nums, target):
614 mp = {}
615
616 for i, x in enumerate(nums):
617 need = target - x
618
619 if need in mp:
620 return [mp[need], i]
621
622 mp[x] = i
Rendered Preview

开放定址法感觉不太好用,刷题也不常见,所以我就没学...就看一下拉链法好了
哈希表:拉链法笔记

  1. 前提条件
    哈希表本质:
    key

    hash函数

    数组下标

例如:hash(key) = key % 10

插入:21

得到:21 % 10 = 1

所以放到:table[1]

  1. 核心概念

如果没有冲突:
table[1] = 21

如果冲突:

21 % 10 = 1
31 % 10 = 1
41 % 10 = 1

都落到下标 1。

拉链法不是往后找空位,而是:

table[1] -> 21 -> 31 -> 41

也就是:数组 + 链表

  1. 结构理解

数组每个位置叫一个桶:

table[0]
table[1]
table[2]
...

每个桶里放一条链表。

例如:

下标: 0 1 2 3
NULL 21->31->41 NULL 13->23

所以查找 31:

31 % 10 = 1

先找到:table[1]

然后在这条链表里找:21 -> 31

  1. 结构体模板

这里我们存两样东西:

key:数组值
value:数组下标

因为两数之和要返回原下标。

#include <stdlib.h>
#include <stdbool.h>

#define HASH_SIZE 10007

struct HashNode {
int key;
int value;
struct HashNode* next;
};

struct HashTable {
struct HashNode* table[HASH_SIZE];
};

  1. 哈希函数

C 里面 % 遇到负数可能得到负结果,所以要处理一下。

int hash(int key)
{
int h = key % HASH_SIZE;

if(h < 0)
{
    h += HASH_SIZE;
}

return h;

}

例如:

key = -3

先:

-3 % 10007 = -3

修正:

-3 + 10007

变成合法下标。

  1. 初始化哈希表
    void initHashTable(struct HashTable* h)
    {
    for(int i = 0; i < HASH_SIZE; i++)
    {
    h->table[i] = NULL;
    }
    }

意思:

每个桶一开始都是空链表

  1. 查找
    struct HashNode* find(struct HashTable* h, int key)
    {
    int index = hash(key);

    struct HashNode* cur = h->table[index];

    while(cur != NULL)
    {
    if(cur->key == key)
    {
    return cur;
    }

     cur = cur->next;
    

    }

    return NULL;
    }

理解:

  1. 先算桶下标
  2. 找到 table[index]
  3. 在链表里顺着 next 找
  4. 插入

这里用头插法。

void insert(struct HashTable* h, int key, int value)
{
int index = hash(key);

struct HashNode* node =
    malloc(sizeof(struct HashNode));

node->key = key;
node->value = value;

node->next = h->table[index];

h->table[index] = node;

}

例如桶里原来:

table[1] -> 21 -> 31

插入 41:

node->next = table[1]
table[1] = node

结果:

table[1] -> 41 -> 21 -> 31

这就是链表头插法。

  1. 释放哈希表

C 里 malloc 了节点,就要 free。

void freeHashTable(struct HashTable* h)
{
for(int i = 0; i < HASH_SIZE; i++)
{
struct HashNode* cur = h->table[i];

    while(cur != NULL)
    {
        struct HashNode* next = cur->next;
        free(cur);
        cur = next;
    }

    h->table[i] = NULL;
}

}

  1. 拉链法完整模板
    #include <stdlib.h>
    #include <stdbool.h>

#define HASH_SIZE 10007

struct HashNode {
int key;
int value;
struct HashNode* next;
};

struct HashTable {
struct HashNode* table[HASH_SIZE];
};

int hash(int key)
{
int h = key % HASH_SIZE;

if(h < 0)
{
    h += HASH_SIZE;
}

return h;

}

void initHashTable(struct HashTable* h)
{
for(int i = 0; i < HASH_SIZE; i++)
{
h->table[i] = NULL;
}
}

struct HashNode* find(struct HashTable* h, int key)
{
int index = hash(key);

struct HashNode* cur = h->table[index];

while(cur != NULL)
{
    if(cur->key == key)
    {
        return cur;
    }

    cur = cur->next;
}

return NULL;

}

void insert(struct HashTable* h, int key, int value)
{
int index = hash(key);

struct HashNode* node =
    malloc(sizeof(struct HashNode));

node->key = key;
node->value = value;

node->next = h->table[index];

h->table[index] = node;

}

void freeHashTable(struct HashTable* h)
{
for(int i = 0; i < HASH_SIZE; i++)
{
struct HashNode* cur = h->table[i];

    while(cur != NULL)
    {
        struct HashNode* next = cur->next;
        free(cur);
        cur = next;
    }

    h->table[i] = NULL;
}

}

  1. LeetCode 1 两数之和:哈希实现
    核心思想

遍历每个数 nums[i]。

当前数是:x = nums[i]

想要另一个数:need = target - x

先去哈希表查:need 是否出现过

如果出现过,直接返回:

need 的下标
当前 i

如果没出现过,就把当前数存进去:

key = nums[i]
value = i

  1. 为什么先查再插?

例如:

nums = [3,3]
target = 6

i = 0:

x = 3
need = 3

表里没有 3。

插入:

3 -> 0

i = 1:

x = 3
need = 3

查到之前的:

3 -> 0

返回:

[0,1]

如果先插再查,可能会把自己和自己配对。

所以:

先查 need
再插 x
13. 两数之和 C 代码
#include <stdlib.h>
#include <stdbool.h>

#define HASH_SIZE 10007

struct HashNode {
int key;
int value;
struct HashNode* next;
};

struct HashTable {
struct HashNode* table[HASH_SIZE];
};

int hash(int key)
{
int h = key % HASH_SIZE;

if(h < 0)
{
    h += HASH_SIZE;
}

return h;

}

void initHashTable(struct HashTable* h)
{
for(int i = 0; i < HASH_SIZE; i++)
{
h->table[i] = NULL;
}
}

struct HashNode* find(struct HashTable* h, int key)
{
int index = hash(key);

struct HashNode* cur = h->table[index];

while(cur != NULL)
{
    if(cur->key == key)
    {
        return cur;
    }

    cur = cur->next;
}

return NULL;

}

void insert(struct HashTable* h, int key, int value)
{
int index = hash(key);

struct HashNode* node =
    malloc(sizeof(struct HashNode));

node->key = key;
node->value = value;

node->next = h->table[index];

h->table[index] = node;

}

void freeHashTable(struct HashTable* h)
{
for(int i = 0; i < HASH_SIZE; i++)
{
struct HashNode* cur = h->table[i];

    while(cur != NULL)
    {
        struct HashNode* next = cur->next;
        free(cur);
        cur = next;
    }
}

}

int* twoSum(int* nums, int numsSize, int target, int* returnSize)
{
struct HashTable h;

initHashTable(&h);

int* ans = malloc(2 * sizeof(int));

*returnSize = 2;

for(int i = 0; i < numsSize; i++)
{
    int x = nums[i];

    int need = target - x;

    struct HashNode* found = find(&h, need);

    if(found != NULL)
    {
        ans[0] = found->value;
        ans[1] = i;

        freeHashTable(&h);

        return ans;
    }

    insert(&h, x, i);
}

*returnSize = 0;

free(ans);
freeHashTable(&h);

return NULL;

}

  1. 例子流程
    nums = [2,7,11,15]
    target = 9

i = 0:

x = 2
need = 7

查 7:

没找到

插入:

2 -> 0

i = 1:

x = 7
need = 2

查 2:

找到了,下标 0

返回:

[0,1]
15. 易错点

  1. key 和 value 不一样

在两数之和里:

key = nums[i]
value = i

不能只存值,因为答案要返回下标。

  1. 负数 hash 要处理
    int h = key % HASH_SIZE;

if(h < 0)
{
h += HASH_SIZE;
}
3. 先查再插

避免同一个元素用两次。

  1. 冲突用链表解决

同一个桶里可能有多个节点:

table[index] -> node1 -> node2 -> node3

查找时要遍历这条链。

笔记版总结
拉链法哈希表


结构:

数组 + 链表

数组每个位置叫桶 bucket

每个桶里是一条链表


哈希函数:

index = key % HASH_SIZE


冲突:

多个 key 得到同一个 index


解决:

把它们挂到同一个桶的链表里


插入:

  1. 计算 index
  2. 创建新节点
  3. 头插到 table[index]

查找:

  1. 计算 index
  2. 遍历 table[index] 链表
  3. 找到 key 返回节点
  4. 找不到返回 NULL

两数之和:

遍历 nums[i]

x = nums[i]

need = target - x

先查 need 是否出现过

如果出现过:
返回 need 的下标和 i

否则:
插入 x 和 i


key = 数值
value = 下标

一句话:

拉链法就是:数组快速定位桶,链表解决同桶冲突;两数之和就是边查 need,边存当前数。
看一下py的字典实现,因为内置了字典,而c需要手写底层hash

def twoSum(nums, target):
mp = {}

for i, x in enumerate(nums):
    need = target - x

    if need in mp:
        return [mp[need], i]

    mp[x] = i