Skip to content

Commit d1e7119

Browse files
committed
feat: add markdown formatter
1 parent c4cad8a commit d1e7119

File tree

75 files changed

+1382
-844
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+1382
-844
lines changed

README.md

+114-88
Large diffs are not rendered by default.

docs/big-data/README.md

+13-11
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
# 海量数据处理
2-
- [如何从大量的 URL 中找出相同的 URL?](/docs/big-data/find-common-urls.md)
3-
- [如何从大量数据中找出高频词?](/docs/big-data/find-top-100-words.md)
4-
- [如何找出某一天访问百度网站最多的 IP?](/docs/big-data/find-top-1-ip.md)
5-
- [如何在大量的数据中找出不重复的整数?](/docs/big-data/find-no-repeat-number.md)
6-
- [如何在大量的数据中判断一个数是否存在?](/docs/big-data/find-a-number-if-exists.md)
7-
- [如何查询最热门的查询串?](/docs/big-data/find-hotest-query-string.md)
8-
- [如何统计不同电话号码的个数?](/docs/big-data/count-different-phone-numbers.md)
9-
- [如何从 5 亿个数中找出中位数?](/docs/big-data/find-mid-value-in-500-millions.md)
10-
- [如何按照 query 的频度排序?](/docs/big-data/sort-the-query-strings-by-counts.md)
11-
- [如何找出排名前 500 的数?](/docs/big-data/find-rank-top-500-numbers.md)
2+
3+
* [如何从大量的 URL 中找出相同的 URL?](/docs/big-data/find-common-urls.md)
4+
* [如何从大量数据中找出高频词?](/docs/big-data/find-top-100-words.md)
5+
* [如何找出某一天访问百度网站最多的 IP?](/docs/big-data/find-top-1-ip.md)
6+
* [如何在大量的数据中找出不重复的整数?](/docs/big-data/find-no-repeat-number.md)
7+
* [如何在大量的数据中判断一个数是否存在?](/docs/big-data/find-a-number-if-exists.md)
8+
* [如何查询最热门的查询串?](/docs/big-data/find-hotest-query-string.md)
9+
* [如何统计不同电话号码的个数?](/docs/big-data/count-different-phone-numbers.md)
10+
* [如何从 5 亿个数中找出中位数?](/docs/big-data/find-mid-value-in-500-millions.md)
11+
* [如何按照 query 的频度排序?](/docs/big-data/sort-the-query-strings-by-counts.md)
12+
* [如何找出排名前 500 的数?](/docs/big-data/find-rank-top-500-numbers.md)
1213

1314
---
1415

1516
## 公众号
17+
1618
GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
1719

1820
<table>
@@ -30,4 +32,4 @@ GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**
3032
</a><br>
3133
</td>
3234
</tr>
33-
</table>
35+
</table>

docs/big-data/count-different-phone-numbers.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
## 如何统计不同电话号码的个数?
22

33
### 题目描述
4+
45
已知某个文件内包含一些电话号码,每个号码为 8 位数字,统计不同号码的个数。
56

67
### 解答思路
8+
79
这道题本质还是求解**数据重复**的问题,对于这类问题,一般首先考虑位图法。
810

911
对于本题,8 位电话号码可以表示的号码个数为 10<sup>8</sup> 个,即 1 亿个。我们每个号码用一个 bit 来表示,则总共需要 1 亿个 bit,内存占用约 100M。
@@ -13,4 +15,5 @@
1315
申请一个位图数组,长度为 1 亿,初始化为 0。然后遍历所有电话号码,把号码对应的位图中的位置置为 1。遍历完成后,如果 bit 为 1,则表示这个电话号码在文件中存在,否则不存在。bit 值为 1 的数量即为 不同电话号码的个数。
1416

1517
### 方法总结
16-
求解数据重复问题,记得考虑位图法。
18+
19+
求解数据重复问题,记得考虑位图法。
+5-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## 如何在大量的数据中判断一个数是否存在?
22

33
### 题目描述
4+
45
给定 40 亿个不重复的没排过序的 unsigned int 型整数,然后再给定一个数,如何快速判断这个数是否在这 40 亿个整数当中?
56

67
### 解答思路
@@ -9,9 +10,11 @@
910
依然可以用分治法解决,方法与前面类似,就不再次赘述了。
1011

1112
#### 方法二:位图法
12-
40 亿个不重复整数,我们用 40 亿个 bit 来表示,初始位均为 0,那么总共需要内存:4,000,000,000b≈512M。
13+
14+
40 亿个不重复整数,我们用 40 亿个 bit 来表示,初始位均为 0,那么总共需要内存:4, 000, 000, 000b≈512M。
1315

1416
我们读取这 40 亿个整数,将对应的 bit 设置为 1。接着读取要查询的数,查看相应位是否为 1,如果为 1 表示存在,如果为 0 表示不存在。
1517

1618
### 方法总结
17-
**判断数字是否存在、判断数字是否重复的问题**,位图法是一种非常高效的方法。
19+
20+
**判断数字是否存在、判断数字是否重复的问题**,位图法是一种非常高效的方法。

docs/big-data/find-common-urls.md

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
## 如何从大量的 URL 中找出相同的 URL?
22

33
### 题目描述
4+
45
给定 a、b 两个文件,各存放 50 亿个 URL,每个 URL 各占 64B,内存限制是 4G。请找出 a、b 两个文件共同的 URL。
56

67
### 解答思路
8+
79
每个 URL 占 64B,那么 50 亿个 URL占用的空间大小约为 320GB。
810

9-
> 5,000,000,000 * 64B ≈ 5GB * 64 = 320GB
11+
> 5, 000, 000, 000 * 64B ≈ 5GB * 64 = 320GB
1012
1113
由于内存大小只有 4G,因此,我们不可能一次性把所有 URL 加载到内存中处理。对于这种类型的题目,一般采用**分治策略**,即:把一个文件中的 URL 按照某个特征划分为多个小文件,使得每个小文件大小不超过 4G,这样就可以把这个小文件读到内存中进行处理了。
1214

1315
**思路如下**
1416

15-
首先遍历文件 a,对遍历到的 URL 求 `hash(URL) % 1000`,根据计算结果把遍历到的 URL 存储到 a<sub>0</sub>, a<sub>1</sub>, a<sub>2</sub>, ..., a<sub>999</sub>,这样每个大小约为 300MB。使用同样的方法遍历文件 b,把文件 b 中的 URL 分别存储到文件 b<sub>0</sub>, b<sub>1</sub>, b<sub>2</sub>, ..., b<sub>999</sub> 中。这样处理过后,所有可能相同的 URL 都在对应的小文件中,即 a<sub>0</sub> 对应 b<sub>0</sub>, ..., a<sub>999</sub> 对应 b<sub>999</sub>,不对应的小文件不可能有相同的 URL。那么接下来,我们只需要求出这 1000 对小文件中相同的 URL 就好了。
17+
首先遍历文件 a,对遍历到的 URL 求 `hash(URL) % 1000` ,根据计算结果把遍历到的 URL 存储到 a<sub>0</sub>, a<sub>1</sub>, a<sub>2</sub>, ..., a<sub>999</sub>,这样每个大小约为 300MB。使用同样的方法遍历文件 b,把文件 b 中的 URL 分别存储到文件 b<sub>0</sub>, b<sub>1</sub>, b<sub>2</sub>, ..., b<sub>999</sub> 中。这样处理过后,所有可能相同的 URL 都在对应的小文件中,即 a<sub>0</sub> 对应 b<sub>0</sub>, ..., a<sub>999</sub> 对应 b<sub>999</sub>,不对应的小文件不可能有相同的 URL。那么接下来,我们只需要求出这 1000 对小文件中相同的 URL 就好了。
1618

17-
接着遍历 a<sub>i</sub>( `i∈[0,999]`),把 URL 存储到一个 HashSet 集合中。然后遍历 b<sub>i</sub> 中每个 URL,看在 HashSet 集合中是否存在,若存在,说明这就是共同的 URL,可以把这个 URL 保存到一个单独的文件中。
19+
接着遍历 a<sub>i</sub>( `i∈[0,999]` ),把 URL 存储到一个 HashSet 集合中。然后遍历 b<sub>i</sub> 中每个 URL,看在 HashSet 集合中是否存在,若存在,说明这就是共同的 URL,可以把这个 URL 保存到一个单独的文件中。
1820

1921
### 方法总结
22+
2023
1. 分而治之,进行哈希取余;
21-
2. 对每个子文件进行 HashSet 统计。
24+
2. 对每个子文件进行 HashSet 统计。
+8-2
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,37 @@
11
## 如何查询最热门的查询串?
22

33
### 题目描述
4+
45
搜索引擎会通过日志文件把用户每次检索使用的所有查询串都记录下来,每个查询串的长度不超过 255 字节。
56

67
假设目前有 1000w 个记录(这些查询串的重复度比较高,虽然总数是 1000w,但如果除去重复后,则不超过 300w 个)。请统计最热门的 10 个查询串,要求使用的内存不能超过 1G。(一个查询串的重复度越高,说明查询它的用户越多,也就越热门。)
78

89
### 解答思路
10+
911
每个查询串最长为 255B,1000w 个串需要占用 约 2.55G 内存,因此,我们无法将所有字符串全部读入到内存中处理。
1012

1113
#### 方法一:分治法
14+
1215
分治法依然是一个非常实用的方法。
1316

1417
划分为多个小文件,保证单个小文件中的字符串能被直接加载到内存中处理,然后求出每个文件中出现次数最多的 10 个字符串;最后通过一个小顶堆统计出所有文件中出现最多的 10 个字符串。
1518

1619
方法可行,但不是最好,下面介绍其他方法。
1720

1821
#### 方法二:HashMap 法
22+
1923
虽然字符串总数比较多,但去重后不超过 300w,因此,可以考虑把所有字符串及出现次数保存在一个 HashMap 中,所占用的空间为 300w*(255+4)≈777M(其中,4表示整数占用的4个字节)。由此可见,1G 的内存空间完全够用。
2024

2125
**思路如下**
2226

23-
首先,遍历字符串,若不在 map 中,直接存入 map,value 记为 1;若在 map 中,则把对应的 value 加 1,这一步时间复杂度 `O(N)`
27+
首先,遍历字符串,若不在 map 中,直接存入 map,value 记为 1;若在 map 中,则把对应的 value 加 1,这一步时间复杂度 `O(N)`
2428

2529
接着遍历 map,构建一个 10 个元素的小顶堆,若遍历到的字符串的出现次数大于堆顶字符串的出现次数,则进行替换,并将堆调整为小顶堆。
2630

27-
遍历结束后,堆中 10 个字符串就是出现次数最多的字符串。这一步时间复杂度 `O(Nlog10)`
31+
遍历结束后,堆中 10 个字符串就是出现次数最多的字符串。这一步时间复杂度 `O(Nlog10)`
2832

2933
#### 方法三:前缀树法
34+
3035
方法二使用了 HashMap 来统计次数,当这些字符串有大量相同前缀时,可以考虑使用前缀树来统计字符串出现的次数,树的结点保存字符串出现次数,0 表示没有出现。
3136

3237
**思路如下**
@@ -36,4 +41,5 @@
3641
最后依然使用小顶堆来对字符串的出现次数进行排序。
3742

3843
### 方法总结
44+
3945
前缀树经常被用来统计字符串的出现次数。它的另外一个大的用途是字符串查找,判断是否有重复的字符串等。

docs/big-data/find-mid-value-in-500-millions.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
## 如何从 5 亿个数中找出中位数?
22

33
### 题目描述
4+
45
从 5 亿个数中找出中位数。数据排序后,位置在最中间的数就是中位数。当样本数为奇数时,中位数为 第 `(N+1)/2` 个数;当样本数为偶数时,中位数为 第 `N/2` 个数与第 `1+N/2` 个数的均值。
56

67
### 解答思路
7-
如果这道题没有内存大小限制,则可以把所有数读到内存中排序后找出中位数。但是最好的排序算法的时间复杂度都为 `O(NlogN)`。这里使用其他方法。
8+
9+
如果这道题没有内存大小限制,则可以把所有数读到内存中排序后找出中位数。但是最好的排序算法的时间复杂度都为 `O(NlogN)` 。这里使用其他方法。
810

911
#### 方法一:双堆法
12+
1013
维护两个堆,一个大顶堆,一个小顶堆。大顶堆中最大的数**小于等于**小顶堆中最小的数;保证这两个堆中的元素个数的差不超过 1。
1114

1215
若数据总数为**偶数**,当这两个堆建好之后,**中位数就是这两个堆顶元素的平均值**。当数据总数为**奇数**时,根据两个堆的大小,**中位数一定在数据多的堆的堆顶**
1316

14-
```java
17+
``` java
1518
class MedianFinder {
1619

1720
private PriorityQueue<Integer> maxHeap;
@@ -55,6 +58,7 @@ class MedianFinder {
5558
以上这种方法,需要把所有数据都加载到内存中。当数据量很大时,就不能这样了,因此,这种方法**适用于数据量较小的情况**。5 亿个数,每个数字占用 4B,总共需要 2G 内存。如果可用内存不足 2G,就不能使用这种方法了,下面介绍另一种方法。
5659

5760
#### 方法二:分治法
61+
5862
分治法的思想是把一个大的问题逐渐转换为规模较小的问题来求解。
5963

6064
对于这道题,顺序读取这 5 亿个数字,对于读取到的数字 num,如果它对应的二进制中最高位为 1,则把这个数字写到 f1 中,否则写入 f0 中。通过这一步,可以把这 5 亿个数划分为两部分,而且 f0 中的数都大于 f1 中的数(最高位是符号位)。
@@ -68,4 +72,5 @@ class MedianFinder {
6872
> **注意**,当数据总数为偶数,如果划分后两个文件中的数据有相同个数,那么中位数就是数据较小的文件中的最大值与数据较大的文件中的最小值的平均值。
6973
7074
### 方法总结
75+
7176
分治法,真香!
+12-8
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,41 @@
11
## 如何在大量的数据中找出不重复的整数?
22

33
### 题目描述
4+
45
在 2.5 亿个整数中找出不重复的整数。注意:内存不足以容纳这 2.5 亿个整数。
56

67
### 解答思路
8+
79
#### 方法一:分治法
810
与前面的题目方法类似,先将 2.5 亿个数划分到多个小文件,用 HashSet/HashMap 找出每个小文件中不重复的整数,再合并每个子结果,即为最终结果。
911

1012
#### 方法二:位图法
13+
1114
**位图**,就是用一个或多个 bit 来标记某个元素对应的值,而键就是该元素。采用位作为单位来存储数据,可以大大节省存储空间。
1215

1316
位图通过使用位数组来表示某些元素是否存在。它可以用于快速查找,判重,排序等。不是很清楚?我先举个小例子。
1417

1518
假设我们要对 `[0,7]` 中的 5 个元素 (6, 4, 2, 1, 5) 进行排序,可以采用位图法。0~7 范围总共有 8 个数,只需要 8bit,即 1 个字节。首先将每个位都置 0:
1619

17-
```
20+
```
1821
0 0 0 0 0 0 0 0
1922
```
2023

2124
然后遍历 5 个元素,首先遇到 6,那么将下标为 6 的位的 0 置为 1;接着遇到 4,把下标为 4 的位 的 0 置为 1:
2225

23-
```
26+
```
2427
0 0 0 0 1 0 1 0
2528
```
2629

2730
依次遍历,结束后,位数组是这样的:
2831

29-
```
32+
```
3033
0 1 1 0 1 1 1 0
3134
```
3235

3336
每个为 1 的位,它的下标都表示了一个数:
3437

35-
```
38+
```
3639
for i in range(8):
3740
if bits[i] == 1:
3841
print(i)
@@ -44,13 +47,14 @@ for i in range(8):
4447

4548
**那么对于这道题**,我们用 2 个 bit 来表示各个数字的状态:
4649

47-
- 00 表示这个数字没出现过;
48-
- 01 表示这个数字出现过一次(即为题目所找的不重复整数);
49-
- 10 表示这个数字出现了多次。
50+
* 00 表示这个数字没出现过;
51+
* 01 表示这个数字出现过一次(即为题目所找的不重复整数);
52+
* 10 表示这个数字出现了多次。
5053

5154
那么这 2<sup>32</sup> 个整数,总共所需内存为 2<sup>32</sup>*2b=1GB。因此,当可用内存超过 1GB 时,可以采用位图法。假设内存满足位图法需求,进行下面的操作:
5255

5356
遍历 2.5 亿个整数,查看位图中对应的位,如果是 00,则变为 01,如果是 01 则变为 10,如果是 10 则保持不变。遍历结束后,查看位图,把对应位是 01 的整数输出即可。
5457

5558
### 方法总结
56-
**判断数字是否重复的问题**,位图法是一种非常高效的方法。
59+
60+
**判断数字是否重复的问题**,位图法是一种非常高效的方法。

docs/big-data/find-rank-top-500-numbers.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
## 如何找出排名前 500 的数?
22

33
### 题目描述
4+
45
有 20 个数组,每个数组有 500 个元素,并且有序排列。如何在这 20*500 个数中找出前 500 的数?
56

67
### 解答思路
8+
79
对于 TopK 问题,最常用的方法是使用堆排序。对本题而言,假设数组降序排列,可以采用以下方法:
810

911
首先建立大顶堆,堆的大小为数组的个数,即为 20,把每个数组最大的值存到堆中。
@@ -14,7 +16,7 @@
1416

1517
> 为了在堆中取出一个数据后,能知道它是从哪个数组中取出的,从而可以从这个数组中取下一个值,可以把数组的指针存放到堆中,对这个指针提供比较大小的方法。
1618
17-
```java
19+
``` java
1820
import lombok.Data;
1921

2022
import java.util.Arrays;
@@ -57,7 +59,6 @@ public class DataWithSource implements Comparable<DataWithSource> {
5759
}
5860
}
5961

60-
6162
class Test {
6263
public static int[] getTop(int[][] data) {
6364
int rowSize = data.length;
@@ -104,4 +105,5 @@ class Test {
104105
```
105106

106107
### 方法总结
107-
求 TopK,不妨考虑一下堆排序?
108+
109+
求 TopK,不妨考虑一下堆排序?

docs/big-data/find-top-1-ip.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
## 如何找出某一天访问百度网站最多的 IP?
22

33
### 题目描述
4+
45
现有海量日志数据保存在一个超大文件中,该文件无法直接读入内存,要求从中提取某天访问百度次数最多的那个 IP。
56

67
### 解答思路
8+
79
这道题只关心某一天访问百度最多的 IP,因此,可以首先对文件进行一次遍历,把这一天访问百度 IP 的相关信息记录到一个单独的大文件中。接下来采用的方法与上一题一样,大致就是先对 IP 进行哈希映射,接着使用 HashMap 统计重复 IP 的次数,最后计算出重复次数最多的 IP。
810

911
> 注:这里只需要找出出现次数最多的 IP,可以不必使用堆,直接用一个变量 max 即可。
1012
1113
### 方法总结
14+
1215
1. 分而治之,进行哈希取余;
1316
2. 使用 HashMap 统计频数;
14-
3. 求解**最大**的 TopN 个,用**小顶堆**;求解**最小**的 TopN 个,用**大顶堆**
17+
3. 求解**最大**的 TopN 个,用**小顶堆**;求解**最小**的 TopN 个,用**大顶堆**

docs/big-data/find-top-100-words.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
## 如何从大量数据中找出高频词?
22

33
### 题目描述
4+
45
有一个 1GB 大小的文件,文件里每一行是一个词,每个词的大小不超过 16B,内存大小限制是 1MB,要求返回频数最高的 100 个词(Top 100)。
56

67
### 解答思路
8+
79
由于内存限制,我们依然无法直接将大文件的所有词一次读到内存中。因此,同样可以采用**分治策略**,把一个大文件分解成多个小文件,保证每个文件的大小小于 1MB,进而直接将单个小文件读取到内存中进行处理。
810

911
**思路如下**
1012

11-
首先遍历大文件,对遍历到的每个词x,执行 `hash(x) % 5000`,将结果为 i 的词存放到文件 a<sub>i</sub> 中。遍历结束后,我们可以得到 5000 个小文件。每个小文件的大小为 200KB 左右。如果有的小文件大小仍然超过 1MB,则采用同样的方式继续进行分解。
13+
首先遍历大文件,对遍历到的每个词x,执行 `hash(x) % 5000` ,将结果为 i 的词存放到文件 a<sub>i</sub> 中。遍历结束后,我们可以得到 5000 个小文件。每个小文件的大小为 200KB 左右。如果有的小文件大小仍然超过 1MB,则采用同样的方式继续进行分解。
1214

13-
接着统计每个小文件中出现频数最高的 100 个词。最简单的方式是使用 HashMap 来实现。其中 key 为词,value 为该词出现的频率。具体方法是:对于遍历到的词 x,如果在 map 中不存在,则执行 `map.put(x, 1)`;若存在,则执行 `map.put(x, map.get(x)+1)`,将该词频数加 1。
15+
接着统计每个小文件中出现频数最高的 100 个词。最简单的方式是使用 HashMap 来实现。其中 key 为词,value 为该词出现的频率。具体方法是:对于遍历到的词 x,如果在 map 中不存在,则执行 `map.put(x, 1)` ;若存在,则执行 `map.put(x, map.get(x)+1)` ,将该词频数加 1。
1416

1517
上面我们统计了每个小文件单词出现的频数。接下来,我们可以通过维护一个**小顶堆**来找出所有词中出现频数最高的 100 个。具体方法是:依次遍历每个小文件,构建一个**小顶堆**,堆大小为 100。如果遍历到的词的出现次数大于堆顶词的出现次数,则用新词替换堆顶的词,然后重新调整为**小顶堆**,遍历结束后,小顶堆上的词就是出现频数最高的 100 个词。
1618

1719
### 方法总结
20+
1821
1. 分而治之,进行哈希取余;
1922
2. 使用 HashMap 统计频数;
20-
3. 求解**最大**的 TopN 个,用**小顶堆**;求解**最小**的 TopN 个,用**大顶堆**
23+
3. 求解**最大**的 TopN 个,用**小顶堆**;求解**最小**的 TopN 个,用**大顶堆**

0 commit comments

Comments
 (0)