239. 滑动窗口最大值
学习视频:单调队列正式登场!| LeetCode:239. 滑动窗口最大值_哔哩哔哩_bilibili
学习文档:代码随想录 (programmercarl.com)
学习时间:14:00-15:30
记录时间:15:41-16:00
状态:已听懂|可单独复写代码|需复习
1. 看到问题后的初始想法与看完随想录后的心得
看到这道题目第一眼的时候我最先想到的是暴力求解,但是在评论中看到暴力求解会超时,遂作罢,直接看视频。看完视频后对于队列有了更新的认识。原来单调队列还可以用来维护滑动窗口中的最大值(单调队列设计的很巧妙)!下面我先来阐述一下单调队列的特性,然后再详细说下单挑队列的构造方法:
单调队列的特性:单调队列从左往右依次为降序排列。(与优先级队列仅仅在表现形式上相同,内核完全不一样)
单调队列的构造方式:单调队列的本质是双向队列,其具有3个方法,第一个是pop操作,具体表现为当我们需要pop的val与单调队列的第一个元素相同时(即滑动窗口向右移动的过程中,滑动窗口或单调队列中最大的数据需要被移出去时),我们把单调队列中第一个元素pop出去。第二个是push操作,如果我们要push的元素比单调队列中最后一个元素大,我们就把最后一个元素pop出去,一直重复到我们要push的元素比单调队列最后一个元素小为止(因为如果push进来的元素比单调队列中最后的元素大,那么说明此时窗口已经划到了我们要push进元素的位置,由于单调队列中最后的元素比当前push进来的更小,并且当前单调队列最后的元素的生命周期比push进来元素更短,说明队列最后的元素在其生命周期内永远也没机会做最大的数字了,因此我们直接将其pop出去。同时这个操作也保证了单调队列的单调性)第三个是getmax(),直接返回队列第一个元素即可。我们python使用deque这个双向队列(底层容器为双向链表来构造单调队列)
代码如下:
from collections import deque
class mydeque:
def __init__(self):
self.queue = deque()
self.size = 0
def pop(self, val):
if self.size != 0 and val == self.queue[0]:
self.queue.popleft()
self.size -= 1
def push(self, val):
while self.size != 0 and val > self.queue[-1]:
self.queue.pop()
self.size -= 1
self.queue.append(val)
self.size += 1
def getmax(self):
max_num = self.queue[0]
return max_num
class Solution(object):
def maxSlidingWindow(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
a = mydeque()
result = []
for i in range(k):
a.push(nums[i])
result.append(a.getmax())
left = 0
right = k
length = len(nums)
while right < length:
a.pop(nums[left])
a.push(nums[right])
result.append(a.getmax())
left += 1
right += 1
return result
347.前 K 个高频元素
学习视频:优先级队列正式登场!大顶堆、小顶堆该怎么用?| LeetCode:347.前 K 个高频元素_哔哩哔哩_bilibili
学习文档:代码随想录 (programmercarl.com)
学习时间:16:00-17:22
记录时间:17:23-17:35
状态:已听懂|可单独复写代码|需复习
1. 看到问题后的初始想法与看完随想录后的心得
刚刚看到这题就想到了使用python中的sort函数对字典中的value从大到小进行排序,最后输出前k个最大值,不过这就使用了python sort的函数了(虽然等一下使用的方法也需要用到heapq,但至少我们知道heapq的实现原理),这道题也就失去了考察的意义。这道题目想让我们设计一个数据结构以对元素出现的频率进行排列。而一般的排列算法性能最好时间复杂度也得o(nlogn),而我们这里并不需要对所有n个元素进行排列,只需要一直对前k个大的元素排列即可。我们这里就使用小顶堆进行操作。小顶堆的特点是父节点小于左右子节点,因此根节点是最小的。我们把小顶堆的个数限制在k个,当有个新的元素插入后,如果小顶堆的个数已经超过k个了,我们就把根节点pop掉。在python中我们使用heapq来快速实现小顶堆(后面会有题目需要我们从底层实现小顶堆,故这里就不自己构造了)
import heapq
class Solution(object):
def topKFrequent(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
k_frequency = {}
min_heap = []
for i in nums:
k_frequency[i] = k_frequency.get(i, 0) + 1
for (key, value) in k_frequency.items():
if min_heap is None or len(min_heap) < k:
heapq.heappush(min_heap, (value, key))
else:
heapq.heappush(min_heap, (value, key))
heapq.heappop(min_heap)
result = []
for i in min_heap:
result.append(i[1])
return result
需要注意的是,python中的heapq是使用数组来实现树结构的,因此min_heap是数组。
栈与队列总结
栈和队列都是基于底层构造的数据结构,所以我们更需要了解栈与队列的底层结构。在python的collections中,栈与队列都是基于deque(双向链表)这个底层数据结构。因此我们可以知道栈和队列的内存地址是不连续的。而并不是所有队列都是基于双向链表的,比如优先级队列,其是基于小顶堆(小顶堆在python中是基于数组的)。