内容简介:优先队列,对比队列而已,顾名思义,就是正常入,按优先级出。可以按小到大,也可以按大到小,或者自定义一个属性,按属性的特征进行出队列。Heap常见的有小顶堆和大顶堆。小顶堆图上演示的是用二叉堆实现的,优先级越小的越排在前面,父亲节点的值比左孩子和右孩子都要小,有兴趣的可以自己实现一个小顶堆。
优先队列,对比队列而已,顾名思义,就是正常入,按优先级出。可以按小到大,也可以按大到小,或者自定义一个属性,按属性的特征进行出队列。
2. 实现机制
2.1 Heap 堆
Heap常见的有小顶堆和大顶堆。
小顶堆图上演示的是用二叉堆实现的,优先级越小的越排在前面,父亲节点的值比左孩子和右孩子都要小,有兴趣的可以自己实现一个小顶堆。
同理大顶堆类似,父亲节点的值比左右孩子的值都要大。
堆的实现其实还有很多种,给大家分享一个链接,Google Heap就能查找的到。 en.wikipedia.org/wiki/Heap_(…
这个内容里面有一张图给大家分享一下
各种堆的实现方式和时间复杂度,有兴趣的可以深入了解一下,刚才上面图片演示的就是Binary实现方式。
2.2 Binary Search Tree二叉搜索树
二叉搜索树,也叫二叉查找树,后面章节会详细介绍。简单介绍一个特性:
- 若任意节点的左⼦树不空,则左⼦树上所有结点的值均⼩于它的 根结点的值;
- 若任意节点的右⼦树不空,则右⼦树上所有结点的值均⼤于它的 根结点的值;
- 任意节点的左、右⼦树也分别为⼆叉搜索树。
简单的说二叉搜索树的值有序的,左孩子的值<父亲节点<右孩子的值,按照树的中序遍历,则是一个按小到大的顺序。
3. 面试题
3.1 703. Kth Largest Element in a Stream 返回数据流中K大的元素
题目要求
Design a class to find the kth largest element in a stream. 设计一个找到数据流中第K大元素的类(class) 复制代码
Example:
int k = 3; int[] arr = [4,5,8,2]; KthLargest kthLargest = new KthLargest(3, arr); kthLargest.add(3); // returns 4 kthLargest.add(5); // returns 5 kthLargest.add(10); // returns 5 kthLargest.add(9); // returns 8 kthLargest.add(4); // returns 8 复制代码
解题思路
假设K=1的话,其实就是从数据流中的最大值,问题就比较简单了,即每次记录最大值即可,比如例子中的[4,5,8,2],那只要每次和记录的Max比较即可。 同理,如果是求第K个最大的元素,有2种解法:
- 第一种: 用一个array保留前K个最大的元素,并将其排序。即每次来一个元素,则与这个array中最小的值比较,如果元素的值比最小的值大,则将array中最小的元素pop掉,将当前元素插入array,并再次排序。 时间复杂度是 N * klogk,klogk是 排序 的时间复杂度(算最快的快排)。
- 第二种: 第一种的话还是有点慢,第二种则采用今天的主题优先队列来实现,每次维护一个小顶堆MinHeap即可。MinHeap的size是k,每次来元素与堆顶的元素比较,比堆顶元素小,则继续往下走,比堆顶元素大,则剔除堆顶元素,将当前元素插入MinHeap,并重新调整堆的顺序。时间复杂度,N * (1 or log2K),最差是每次都需要调整堆,调整堆的时间复杂度是Log2K。如果不需要调整,则是O(1)。
代码实现
代码实现以第二种方式。
import heapq class KthLargest(object): def __init__(self, k, nums): """ :type k: int :type nums: List[int] """ self.k = k self.min_heap = [] for i in nums: self.add(i) def add(self, val): """ :type val: int :rtype: int """ if len(self.min_heap) < self.k: heapq.heappush(self.min_heap, val) else: if val > self.min_heap[0]: heapq.heappop(self.min_heap) heapq.heappush(self.min_heap, val) return self.min_heap[0] 复制代码
这里用了 python 内置实现小顶堆的模块heapq,如果是别的语言,应该也有对应的内部实现堆的函数。heappush就是往堆里面插入元素,函数会再次调整好顺序,heappop就是讲堆顶元素pop。代码实现思路与上面文字描述一致。
3.2 239. Sliding Window Maximum 返回窗口的最大值
题目要求给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。 返回滑动窗口最大值。
Example:
Input: nums = [1,3,-1,-3,5,3,6,7], and k = 3 Output: [3,3,5,5,6,7] Explanation: Window position Max --------------- ----- [1 3 -1] -3 5 3 6 7 3 1 [3 -1 -3] 5 3 6 7 3 1 3 [-1 -3 5] 3 6 7 5 1 3 -1 [-3 5 3] 6 7 5 1 3 -1 -3 [5 3 6] 7 6 1 3 -1 -3 5 [3 6 7] 7 复制代码
解题思路此题也有两种解法。
- 第一种解法: 采用大顶端实现,维护一个size=k的MaxHeap,同时再维护一个Count的Map,记录每一个值得位置。比如开始[1,3,-1],则堆顶是3,返回最大值是3,如果再来一个-3,比3小,堆顶依然是3,但是最初的1需要从堆中去掉,将-3加入进来。所以需要维护这个堆,删除旧元素,加入新元素,同时结果就是堆顶。代码会待会给大家演示。时间复杂度是 NlogK 。
- 第二种: 第一种还是比较复杂的,还可以简化,题目的特点是每次需要维护的窗口是一定的,即size=k,那么可以使用上一篇讲的Queue实现,但是有点区别是这里需要使用双端队列deque,即两边都可以进和出。怎么实现了? 将k个元素依次加入到deque里,同时每次一个新元素进来的话进行队列的维护。 重点就在维护的逻辑,首先保证deque的长度不能大于k, 同时最左边的元素永远是最大的元素。 比如[1,3,-1],那么deque维护之后就变成[3, -1],因为1比3小,还比3老,那肯定不会是我们要找的元素,结果就是3。再加一个-3,deque就变成[3,-1,-3],因为-3比3小,所以加到最新的位置即可,结果还是3。再来一个5,首先将旧元素-3剔除,变成[-1,-3,5],如果比较这三个元素,发现-1,-3都比5小,则全部再次剔除变成[5],所以结果是5。 文字描述举例输出结果就是[3,3,5]
代码实现
- 第一种解法: MaxHeap实现
import collections import heapq class Solution(object): def maxSlidingWindow(self, nums, k): """ :type nums: List[int] :type k: int :rtype: List[int] """ count_map, max_heap, result = collections.Counter(), [], [] for i, num in enumerate(nums): heapq.heappush(max_heap, -num) count_map[num] += 1 # 记录每个元素的count while not count_map[-max_heap[0]]: # 清除堆顶的旧元素 heapq.heappop(max_heap) if i >= k - 1: result.append(-max_heap[0]) count_map[nums[i - k + 1]] -= 1 # 将位置在k之前的元素count变为0 return result 复制代码
大顶端还是使用heapq实现,CountMap使用python内部实现的Counter(),里面就是一个Map,key是num,value是count。需要说明的是,heapq默认是小顶堆,如果需要实现大顶端,需要小技巧,就是将num变负数,比如[1,2,3],默认情况下是堆顶是1,但是变负数-num,[-1,-2,-3],则就变成堆顶是-3,从而实现了MaxHeap。 还有一个注意的是,这里没有每次去不是堆顶的且count=0的旧元素,而是等它变成堆顶的情况,如果发现是少于size=k之前的元素,则清除。这样比较好实现一点,可能理解上稍微变难了点。
- 第二种解法: deque双端队列 (推荐解法)
class Solution(object): """ deque实现 """ def maxSlidingWindow(self, nums, k): """ :type nums: List[int] :type k: int :rtype: List[int] """ if not nums: return [] result, window = [], [] for i, num in enumerate(nums): if i >= k and window[0] <= i - k: window.pop(0) # 从队列最左边清除旧元素 while window and nums[window[-1]] <= num: window.pop() # 从队列右边清除比当前元素小的元素 window.append(i) if i >= k - 1: result.append(nums[window[0]]) return result 复制代码
第二种解法采用双端队列,循环枚举nums,window是记录nums元素的下标,而不是值,如果发现window最左边的元素下标超过i-k,则元素过时,需要从队列的最左边将元素剔除。如果发现window最右边的元素比当前元素小,则也全部剔除,当前元素肯定比这些元素小的要更新,将当前元素插入window即可,同时取window最左边的值为结果,因为最左边的值永远最大。
完整代码已上传github github.com/CrystalSkyZ…
更多算法文章请关注小专栏 xiaozhuanlan.com/leetcode_tc
更多精彩文章请关注公众号 天澄的技术笔记 (ChengTian_Tech)
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Release It!
Michael T. Nygard / Pragmatic Bookshelf / 2007-03-30 / USD 34.95
“Feature complete” is not the same as “production ready.” Whether it’s in Java, .NET, or Ruby on Rails, getting your application ready to ship is only half the battle. Did you design your system to......一起来看看 《Release It!》 这本书的介绍吧!