例题一
解法(利⽤堆):
算法思路:
其实就是⼀个模拟的过程:
•
每次从⽯堆中拿出最⼤的元素以及次⼤的元素,然后将它们粉碎;
•
如果还有剩余,就将剩余的⽯头继续放在原始的⽯堆⾥⾯。
重复上⾯的操作,直到⽯堆⾥⾯只剩下⼀个元素,或者没有元素(因为所有的⽯头可能全部抵消了)
那么主要的问题就是解决:
•
如何顺利的拿出最⼤的⽯头以及次⼤的⽯头;
•
并且将粉碎后的⽯头放⼊⽯堆中之后,也能快速找到下⼀轮粉碎的最⼤⽯头和次⼤⽯头;
这不正好可以利⽤堆的特性来实现嘛?
•
我们可以创建⼀个⼤根堆;
•
然后将所有的⽯头放⼊⼤根堆中;
•
每次拿出前两个堆顶元素粉碎⼀下,如果还有剩余,就将剩余的⽯头继续放⼊堆中;
这样就能快速的模拟出这个过程。
![](https://img-blog.csdnimg.cn/direct/a79a98aa73b647cca01f7e36f9d5c43d.png)
例题二
解法(优先级队列):
算法思路:
我相信,看到
TopK
问题的时候,兄弟们应该能⽴⻢想到「堆」,这应该是刻在⻣⼦⾥的记忆。
![](https://img-blog.csdnimg.cn/direct/b1374067e74140ffbc8486ee05a396ac.png)
例题三
解法(堆):
算法思路:
•
稍微处理⼀下原数组:
a.
我们需要知道每⼀个单词出现的频次,因此可以先使⽤哈希表,统计出每⼀个单词出现的频 次;
b.
然后在哈希表中,选出前 k ⼤的单词(为什么不在原数组中选呢?因为原数组中存在重复的单 词,哈希表⾥⾯没有重复单词,并且还有每⼀个单词出现的频次)
•
如何使⽤堆,拿出前 k ⼤元素:
a.
先定义⼀个⾃定义排序,我们需要的是前
k
⼤,因此需要⼀个⼩根堆。但是当两个字符串的频次相同的时候,我们需要的是字典序较⼩的,此时是⼀个⼤根堆的属性,在定义⽐较器的时候需要注意!
▪
当两个字符串出现的频次不同的时候:需要的是基于频次⽐较的⼩根堆
▪
当两个字符串出现的频次相同的时候:需要的是基于字典序⽐较的⼤根堆
b.
定义好⽐较器之后,依次将哈希表中的字符串插⼊到堆中,维持堆中的元素不超过
k
个;
c.
遍历完整个哈希表后,堆中的剩余元素就是前
k
⼤的元素
例题四
解法(利⽤两个堆):
算法思路:
这是⼀道关于「堆」这种数据结构的⼀个「经典应⽤」。
我们可以将整个数组「按照⼤⼩」平分成两部分(如果不能平分,那就让较⼩部分的元素多⼀个),较⼩的部分称为左侧部分,较⼤的部分称为右侧部分:
•
将左侧部分放⼊「⼤根堆」中,然后将右侧元素放⼊「⼩根堆」中;
•
这样就能在 O(1) 的时间内拿到中间的⼀个数或者两个数,进⽽求的平均数。
如下图所⽰:
![](https://img-blog.csdnimg.cn/direct/a7a30883d1e74babb3808d3e5d59df9f.png)
于是问题就变成了「如何将⼀个⼀个从数据流中过来的数据,动态调整到⼤根堆或者⼩根堆中,并且保证两个堆的元素⼀致,或者左侧堆的元素⽐右侧堆的元素多⼀个」,为了⽅便叙述,将左侧的「⼤根堆」记为 left
,右侧的「⼩根堆」记为
right
,数据流中来的「数据」记为 x
。其实,就是⼀个「分类讨论」的过程:
1.
如果左右堆的「数量相同」,
left.size() == right.size()
:
a.
如果两个堆都是空的,直接将数据
x
放⼊到
left
中;
b.
如果两个堆⾮空:
i.
如果元素要放⼊左侧,也就是
x <= left.top()
:那就直接放,因为不会影响我们制定的规则;
ii.
如果要放⼊右侧
•
可以先将
x
放⼊
right
中,
•
然后把
right
的堆顶元素放⼊
left
中 ;
2.
如果左右堆的数量「不相同」,那就是
left.size() > right.size()
:
a.
这个时候我们关⼼的是
x
是否会放⼊
left
中,导致
left
变得过多:
i.
如果
x
放⼊
right
中,也就是
x >= right.top()
,直接放;
ii.
反之,就是需要放⼊
left
中:
•
可以先将
x
放⼊
left
中,
•
然后把
left
的堆顶元素放⼊
right
中 ;
只要每⼀个新来的元素按照「上述规则」执⾏,就能保证
left
中放着整个数组排序后的「左半部 分」, right
中放着整个数组排序后的「右半部分」,就能在
O(1)
的时间内求出平均数。
![](https://img-blog.csdnimg.cn/direct/0d7fa91c383e4c968b7606106a00814f.png)