Skip to content

Commit 421ebd3

Browse files
author
lucifer
committed
fix: 若干小修复
1 parent 25aa0f6 commit 421ebd3

File tree

4 files changed

+112
-103
lines changed

4 files changed

+112
-103
lines changed

problems/295.find-median-from-data-stream.md

Lines changed: 102 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ double findMedian() - 返回目前所有元素的中位数。
2222
addNum(1)
2323
addNum(2)
2424
findMedian() -> 1.5
25-
addNum(3)
25+
addNum(3)
2626
findMedian() -> 2
2727
进阶:
2828
@@ -41,7 +41,7 @@ findMedian() -> 2
4141
- 阿里
4242
- 百度
4343
- 字节
44-
44+
4545
## 思路
4646

4747
这道题目是求动态数据的中位数,在 leetcode 难度为`hard`. 如果这道题是求静态数据的中位数,我们用数组去存储,
@@ -88,6 +88,7 @@ function findMedian(a) {
8888
再比如对于[1,2,3, 4] 求中位数:
8989

9090
![295.find-median-from-data-stream-2](https://tva1.sinaimg.cn/large/007S8ZIlly1ghlty1ld04j30mx06ljrm.jpg)
91+
9192
## 关键点解析
9293

9394
- 用两个堆(一个大顶堆,一个小顶堆)来简化时间复杂度
@@ -96,15 +97,20 @@ function findMedian(a) {
9697
> JavaScript 不像 Java, C++等语言都有`优先级队列`中这种数据结构, 因此大家可以使用社区的实现
9798
> 个人认为没有非要纠结于优先级队列怎么实现, 至少这道题不是考这个的
9899
> 优先级队列的实现个人认为已经超过了这道题想考察的范畴
100+
99101
## 代码
100102

103+
代码支持:CPP,JS
104+
105+
JS Code:
106+
101107
如果不使用现成的`优先级队列`这种数据结构,代码可能是这样的:
102108

103109
```js
104110
/**
105111
* initialize your data structure here.
106112
*/
107-
var MedianFinder = function() {
113+
var MedianFinder = function () {
108114
this.maxHeap = [];
109115
this.minHeap = [];
110116
};
@@ -118,7 +124,8 @@ function minHeapify() {
118124
// 其实可以降到O(logn), 具体细节我不想在这里讲解和实现
119125
for (let i = a.length - 1; i >> 1 > 0; i--) {
120126
// 自下往上堆化
121-
if (a[i] < a[i >> 1]) { // 如果子元素更小,则交换位置
127+
if (a[i] < a[i >> 1]) {
128+
// 如果子元素更小,则交换位置
122129
const temp = a[i];
123130
this.minHeap[i] = a[i >> 1];
124131
this.minHeap[i >> 1] = temp;
@@ -136,7 +143,8 @@ function maxHeapify() {
136143
// 其实可以降到O(logn), 具体细节我不想在这里讲解和实现
137144
for (let i = a.length - 1; i >> 1 > 0; i--) {
138145
// 自下往上堆化
139-
if (a[i] > a[i >> 1]) { // 如果子元素更大,则交换位置
146+
if (a[i] > a[i >> 1]) {
147+
// 如果子元素更大,则交换位置
140148
const temp = a[i];
141149
this.maxHeap[i] = a[i >> 1];
142150
this.maxHeap[i >> 1] = temp;
@@ -149,7 +157,7 @@ function maxHeapify() {
149157
* @param {number} num
150158
* @return {void}
151159
*/
152-
MedianFinder.prototype.addNum = function(num) {
160+
MedianFinder.prototype.addNum = function (num) {
153161
// 为了大家容易理解,这部分代码写的比较冗余
154162

155163
// 插入
@@ -185,7 +193,7 @@ MedianFinder.prototype.addNum = function(num) {
185193
/**
186194
* @return {number}
187195
*/
188-
MedianFinder.prototype.findMedian = function() {
196+
MedianFinder.prototype.findMedian = function () {
189197
if ((this.maxHeap.length + this.minHeap.length) % 2 === 0) {
190198
return (this.minHeap[0] + this.maxHeap[0]) / 2;
191199
} else {
@@ -201,30 +209,28 @@ MedianFinder.prototype.findMedian = function() {
201209
*/
202210
```
203211

204-
其中`minHeapify` `maxHeapify` 的过程都有一个hack操作,就是:
212+
其中`minHeapify``maxHeapify` 的过程都有一个 hack 操作,就是:
205213

206214
```js
207-
208215
this.heap.unshift(null);
209216
// ....
210217
this.heap.shift(null);
211-
212218
```
213219

214-
其实就是为了存储的数据从1开始,这样方便计算。 即对于下标为i的元素`i >> 1` 一定是父节点的下标。
220+
其实就是为了存储的数据从 1 开始,这样方便计算。 即对于下标为 i 的元素`i >> 1` 一定是父节点的下标。
215221

216222
![295.find-median-from-data-stream-3](https://tva1.sinaimg.cn/large/007S8ZIlly1ghlty4xqrrj30n706z3yu.jpg)
217223

218224
> 这是因为我用满二叉树来存储的堆
219225
220-
这个实现比较繁琐,下面介绍一种优雅的方式,假设JS和Java和C++等语言一样有`PriorityQueue`这种数据结构,那么我们实现就比较简单了。
226+
这个实现比较繁琐,下面介绍一种优雅的方式,假设 JS 和 Java 和 C++等语言一样有`PriorityQueue`这种数据结构,那么我们实现就比较简单了。
221227

222228
代码:
223229

224-
> 关于PriorityQueue的实现,感兴趣的可以看下 https://github.com/janogonzalez/priorityqueuejs
230+
> 关于 PriorityQueue 的实现,感兴趣的可以看下 https://github.com/janogonzalez/priorityqueuejs
225231
226232
```js
227-
var MedianFinder = function() {
233+
var MedianFinder = function () {
228234
this.maxHeap = new PriorityQueue((a, b) => a - b);
229235
this.minHeap = new PriorityQueue((a, b) => b - a);
230236
};
@@ -233,37 +239,38 @@ var MedianFinder = function() {
233239
* @param {number} num
234240
* @return {void}
235241
*/
236-
MedianFinder.prototype.addNum = function(num) {
237-
// 我们的目标就是建立两个堆,一个大顶堆,一个小顶堆
238-
// 结合中位数的特点
239-
// 这两个堆需要满足:
240-
// 1. 大顶堆元素都比小顶堆小(由于堆的特点其实只要比较堆顶即可)
241-
// 2. 大顶堆元素不小于小顶堆,且最多比小顶堆多一个元素
242-
243-
// 满足上面两个条件的话,如果想要找到中位数,就比较简单了
244-
// 如果两个堆数量相等(本质是总数为偶数), 就两个堆顶元素的平均数
245-
// 如果两个堆数量不相等(本质是总数为奇数), 就取大顶堆的堆顶元素
246-
247-
// 问题如果保证满足上述两个特点
248-
249-
// 1. 保证第一点
250-
this.maxHeap.enq(num);
251-
// 由于小顶堆的所有数都来自大顶堆的堆顶元素(最大值)
252-
// 因此可以保证第一点
253-
this.minHeap.enq(this.maxHeap.deq());
254-
255-
// 2. 保证第二点
256-
if (this.maxHeap.size() < this.minHeap.size()){
257-
this.maxHeap.enq(this.minHeap.deq());
258-
}
242+
MedianFinder.prototype.addNum = function (num) {
243+
// 我们的目标就是建立两个堆,一个大顶堆,一个小顶堆
244+
// 结合中位数的特点
245+
// 这两个堆需要满足:
246+
// 1. 大顶堆元素都比小顶堆小(由于堆的特点其实只要比较堆顶即可)
247+
// 2. 大顶堆元素不小于小顶堆,且最多比小顶堆多一个元素
248+
249+
// 满足上面两个条件的话,如果想要找到中位数,就比较简单了
250+
// 如果两个堆数量相等(本质是总数为偶数), 就两个堆顶元素的平均数
251+
// 如果两个堆数量不相等(本质是总数为奇数), 就取大顶堆的堆顶元素
252+
253+
// 问题如果保证满足上述两个特点
254+
255+
// 1. 保证第一点
256+
this.maxHeap.enq(num);
257+
// 由于小顶堆的所有数都来自大顶堆的堆顶元素(最大值)
258+
// 因此可以保证第一点
259+
this.minHeap.enq(this.maxHeap.deq());
260+
261+
// 2. 保证第二点
262+
if (this.maxHeap.size() < this.minHeap.size()) {
263+
this.maxHeap.enq(this.minHeap.deq());
264+
}
259265
};
260266

261267
/**
262268
* @return {number}
263269
*/
264-
MedianFinder.prototype.findMedian = function() {
265-
if (this.maxHeap.size() == this.minHeap.size()) return (this.maxHeap.peek() + this.minHeap.peek()) / 2.0;
266-
else return this.maxHeap.peek();
270+
MedianFinder.prototype.findMedian = function () {
271+
if (this.maxHeap.size() == this.minHeap.size())
272+
return (this.maxHeap.peek() + this.minHeap.peek()) / 2.0;
273+
else return this.maxHeap.peek();
267274
};
268275

269276
/**
@@ -272,5 +279,60 @@ MedianFinder.prototype.findMedian = function() {
272279
* obj.addNum(num)
273280
* var param_2 = obj.findMedian()
274281
*/
282+
```
275283

284+
CPP Code:
285+
286+
```cpp
287+
class MedianFinder {
288+
public:
289+
/** initialize your data structure here. */
290+
MedianFinder() {
291+
292+
}
293+
294+
void addNum(int num) {
295+
if (big_queue.empty()) {
296+
big_queue.push(num);
297+
return;
298+
}
299+
if (big_queue.size() == small_queue.size()) {
300+
if (num <= big_queue.top()) {
301+
big_queue.push(num);
302+
} else {
303+
small_queue.push(num);
304+
}
305+
} else if (big_queue.size() > small_queue.size()) {
306+
if (big_queue.top() > num) {
307+
small_queue.push(big_queue.top());
308+
big_queue.pop();
309+
big_queue.push(num);
310+
} else {
311+
small_queue.push(num);
312+
}
313+
} else if (big_queue.size() < small_queue.size()) {
314+
if (small_queue.top() > num) {
315+
big_queue.push(num);
316+
} else {
317+
big_queue.push(small_queue.top());
318+
small_queue.pop();
319+
small_queue.push(num);
320+
}
321+
}
322+
}
323+
324+
double findMedian() {
325+
if (big_queue.size() == small_queue.size()) {
326+
return (big_queue.top() + small_queue.top()) * 0.5;
327+
}
328+
if (big_queue.size() < small_queue.size()) {
329+
return small_queue.top();
330+
}
331+
return big_queue.top();
332+
}
333+
334+
private:
335+
std::priority_queue<int, std::vector<int>, std::greater<int>> small_queue; // 最小堆
336+
std::priority_queue<int> big_queue; // 最大堆
337+
};
276338
```

problems/472.concatenated-words.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ https://leetcode-cn.com/problems/concatenated-words/
6666

6767
由于我们并不知道 cat 这里断开,结果更大?还是 cats 这里断开结果更大?因此我们的做法是将其全部递归求出,然后取出最大值即可。如果我们直接这样递归的话,可能会超时,卡在最后一个测试用例上。一个简单的方式是记忆化递归,从而避免重复计算,经测试这种方法能够通过。
6868

69+
## 关键点分析
70+
71+
- 前缀树
72+
6973
## 代码
7074

7175
代码支持:Python3
@@ -118,10 +122,6 @@ class Solution:
118122
return res
119123
```
120124

121-
## 关键点分析
122-
123-
- 前缀树
124-
125125
## 相关题目
126126

127127
- [0208.implement-trie-prefix-tree](https://github.com/azl397985856/leetcode/blob/b8e8fa5f0554926efa9039495b25ed7fc158372a/problems/208.implement-trie-prefix-tree.md)

thinkings/union-find.en.md

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Union Find Data Structure
22

3-
Leetcode has many problems concerning the union-find data structure. To be specific, the official number is 30(until 2020-02-20). And some problems, though not labeled with `Union Find`, can be solved more easily by applying this data structure. A problem-solving pattern can be found among this kind of problem. Once you have grasped the pattern, you can solve these problems with higher speed and fewer mistakes, which is the benefit of using patterns.
3+
Leetcode has many problems concerning the union-find data structure. To be specific, the official number is 30(until 2020-02-20). And some problems, though not labeled with `Union Find`, can be solved more easily by applying this data structure. A problem-solving pattern can be found among this kind of problem. Once you have grasped the pattern, you can solve these problems with higher speed and fewer mistakes, which is the benefit of using patterns.
44

55
Related problems:
66

@@ -100,9 +100,9 @@ class UF:
100100

101101
def find(self, x):
102102
while x != self.parent[x]:
103-
x = self.parent[x]
104103
# path compression
105104
self.parent[x] = self.parent[self.parent[x]];
105+
x = self.parent[x]
106106
return x
107107
def union(self, p, q):
108108
if self.connected(p, q): return
@@ -117,30 +117,3 @@ class UF:
117117
def connected(self, p, q):
118118
return self.find(p) == self.find(q)
119119
```
120-
121-
The code above implements path compression with recursion, which, though is easier to write, contains the risk of stack overflow. The following is how we can do it with iteration.
122-
123-
```python
124-
class UF:
125-
parent = {}
126-
def __init__(self, equations):
127-
# Initiation
128-
129-
def find(self, x):
130-
# root
131-
r = x
132-
while r != parent[r]:
133-
r = parent[r]
134-
k = x
135-
while k != r:
136-
# Store the parent node of parent[k] temporarily.
137-
j = parent[k]
138-
parent[k] = r
139-
k = j
140-
return r
141-
def union(self, p, q):
142-
if self.connected(p, q): return
143-
self.parent[self.find(p)] = self.find(q)
144-
def connected(self, p, q):
145-
return self.find(p) == self.find(q)
146-
```

thinkings/union-find.md

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
- [721. 账户合并](https://leetcode-cn.com/problems/accounts-merge/solution/mo-ban-ti-bing-cha-ji-python3-by-fe-lucifer-3/)
99
- [990. 等式方程的可满足性](https://github.com/azl397985856/leetcode/issues/304)
1010
- [1202. 交换字符串中的元素](https://leetcode-cn.com/problems/smallest-string-with-swaps/)
11+
- [1697. 检查边长度限制的路径是否存在](https://leetcode-cn.com/problems/checking-existence-of-edge-length-limited-paths/)
1112

12-
看完这里的内容,建议拿上面的题目练下手,检测一下学习成果。
13+
上面的题目前面四道都是无权图的连通性问题,第五道题是带权图的连通性问题。两种类型大家都要会,上面的题目关键字都是**连通性**,代码都是套模板。看完这里的内容,建议拿上面的题目练下手,检测一下学习成果。
1314

1415
## 概述
1516

@@ -101,9 +102,9 @@ class UF:
101102

102103
def find(self, x):
103104
while x != self.parent[x]:
104-
x = self.parent[x]
105105
# 路径压缩
106106
self.parent[x] = self.parent[self.parent[x]];
107+
x = self.parent[x]
107108
return x
108109
def union(self, p, q):
109110
if self.connected(p, q): return
@@ -121,35 +122,8 @@ class UF:
121122
return self.find(p) == self.find(q)
122123
```
123124

124-
上面是递归的方式进行路径压缩,写起来比较简单。但是有栈溢出的风险。 接下来我们看下迭代的写法:
125-
126-
```python
127-
class UF:
128-
parent = {}
129-
def __init__(self, equations):
130-
# 做一些初始化操作
131-
132-
def find(self, x):
133-
# 根节点
134-
r = x
135-
while r != parent[r]:
136-
r = parent[r]
137-
k = x
138-
while k != r:
139-
# 暂存parent[k]的父节点
140-
j = parent[k]
141-
parent[k] = r
142-
k = j
143-
return r
144-
def union(self, p, q):
145-
if self.connected(p, q): return
146-
self.parent[self.find(p)] = self.find(q)
147-
def connected(self, p, q):
148-
return self.find(p) == self.find(q)
149-
```
150-
151125
## 总结
152126

153127
如果题目有连通,等价的关系,那么你就可以考虑并查集,使用并查集的时候要注意路径压缩。
154128

155-
本文提供的题目模板是西法我用的比较多的,用了它不仅出错概率大大降低,而且速度也快了很多,整个人都更自信了呢 ^_^
129+
本文提供的题目模板是西法我用的比较多的,用了它不仅出错概率大大降低,而且速度也快了很多,整个人都更自信了呢 ^\_^

0 commit comments

Comments
 (0)