239. 滑动窗口最大值 - 力扣(Leetcode)
因为时间关系直接读代码啦,这里是实现了一个单调队列,注意调用deque中的方法有popleft、append以及pop
from collections import deque
class MyQueue: #单调队列(从大到小
def __init__(self):
self.queue = deque() #这里需要使用deque实现单调队列,直接使用list会超时
#每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
#同时pop之前判断队列当前是否为空。
def pop(self, value):
if self.queue and value == self.queue[0]:
self.queue.popleft()#list.pop()时间复杂度为O(n),这里需要使用collections.deque()
#如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
#这样就保持了队列里的数值是单调从大到小的了。
def push(self, value):
while self.queue and value > self.queue[-1]:
self.queue.pop()
self.queue.append(value)
#查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
def front(self):
return self.queue[0]
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
que = MyQueue()
result = []
for i in range(k): #先将前k的元素放进队列
que.push(nums[i])
result.append(que.front()) #result 记录前k的元素的最大值
for i in range(k, len(nums)):
que.pop(nums[i - k]) #滑动窗口移除最前面元素
que.push(nums[i]) #滑动窗口前加入最后面的元素
result.append(que.front()) #记录对应的最大值
return result
下面是GPT4生成的代码,更加简洁易懂
from collections import deque
def maxSlidingWindow(nums, k):
if not nums:
return []
window = deque()
result = []
for i, x in enumerate(nums):
# 如果滑动窗口左边界存在元素,且左边界元素小于当前元素,我们就将其从窗口中移除
while window and nums[window[-1]] <= x:
window.pop()
# 新元素从右边入窗口
window.append(i)
# 当左边界元素的索引小于滑动窗口的左边界,我们就将其从窗口中移除
if window[0] == i - k:
window.popleft()
# 当窗口的大小达到k的时候开始记录答案
if i >= k - 1:
result.append(nums[window[0]])
return result
347. 前 K 个高频元素 - 力扣(Leetcode)
时间原因,直接看代码,用到了heapq的heappush和heappop
#时间复杂度:O(nlogk)
#空间复杂度:O(n)
import heapq
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
#要统计元素出现频率
map_ = {} #nums[i]:对应出现的次数
for i in range(len(nums)):
map_[nums[i]] = map_.get(nums[i], 0) + 1
#对频率排序
#定义一个小顶堆,大小为k
pri_que = [] #小顶堆
#用固定大小为k的小顶堆,扫描所有频率的数值
for key, freq in map_.items():
heapq.heappush(pri_que, (freq, key))
if len(pri_que) > k: #如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
heapq.heappop(pri_que)
#找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
result = [0] * k
for i in range(k-1, -1, -1):
result[i] = heapq.heappop(pri_que)[1]
return result
GPT4给出的代码更为简单
import collections
import heapq
def topKFrequent(nums, k):
count = collections.Counter(nums)
return heapq.nlargest(k, count.keys(), key=count.get)
这段代码首先用collections.Counter计算每个元素出现的频率,然后用heapq.nlargest来找出频率最高的k个元素。
这个算法的时间复杂度是O(n log k),其中n是数组的长度,因为Counter的计算需要O(n)的时间,而heapq.nlargest需要O(n log k)的时间。
需要注意的是,heapq.nlargest函数返回的结果可能不是按频率降序排序的,如果需要按频率降序排序的结果,可以使用sorted函数再排序一次。
下面是不调包的解法
def topKFrequent(nums, k):
# Step 1: 构建哈希表,记录每个元素的频率
freq_map = {}
for num in nums:
if num in freq_map:
freq_map[num] += 1
else:
freq_map[num] = 1
# Step 2: 将哈希表的键值对转化为列表,按照频率进行降序排序
freq_list = [(key, value) for key, value in freq_map.items()]
freq_list.sort(key=lambda x: x[1], reverse=True)
# Step 3: 提取出频率最高的k个元素
return [freq_list[i][0] for i in range(k)]
关于小顶堆
小顶堆(也称为最小堆)是一种特殊的树形数据结构,它满足以下性质:
- 它是一颗完全二叉树:除了最底层,其它所有层的节点数都达到最大,最底层的节点都连续集中在左侧。
- 任意节点的值都不大于其子节点的值。这意味着最小的元素总是位于根节点。
小顶堆常常被用于解决需要快速找到一组数据中最小值的问题。它的主要操作有:
- 插入:将新的元素插入到堆中,并重新调整堆的结构使其保持小顶堆的性质。时间复杂度是O(log n)。
- 删除最小值:删除根节点(即当前的最小值),并重新调整堆的结构使其保持小顶堆的性质。时间复杂度是O(log n)。
- 堆化:将一个无序的数组调整为一个小顶堆。时间复杂度是O(n)。
以下是一个使用Python实现小顶堆的基本代码:
class MinHeap:
def __init__(self):
self.heap = []
def parent(self, i):
return (i - 1) // 2
def left_child(self, i):
return 2 * i + 1
def right_child(self, i):
return 2 * i + 2
def insert(self, key):
self.heap.append(key)
self.heapify_up(len(self.heap) - 1)
def heapify_up(self, i):
while (i != 0) and (self.heap[self.parent(i)] > self.heap[i]):
self.heap[i], self.heap[self.parent(i)] = self.heap[self.parent(i)], self.heap[i]
i = self.parent(i)
def extract_min(self):
if self.size() == 1:
return self.heap.pop()
root = self.heap[0]
self.heap[0] = self.heap[-1]
self.heap.pop()
self.heapify_down(0)
return root
def heapify_down(self, i):
left = self.left_child(i)
right = self.right_child(i)
smallest = i
if (left < self.size()) and (self.heap[i] > self.heap[left]):
smallest = left
if (right < self.size()) and (self.heap[smallest] > self.heap[right]):
smallest = right
if smallest != i:
self.heap[i], self.heap[smallest] = self.heap[smallest], self.heap[i]
self.heapify_down(smallest)
def size(self):
return len(self.heap)
这个类实现了小顶堆的基本操作,包括插入一个元素,删除最小元素,以及从一个元素开始向上或向下调整堆的结构。
关于快排
快速排序是一种使用分治策略(Divide and Conquer)的排序算法。其基本思想是,选择一个基准元素,将小于基准元素的值放在其左边,将大于或等于基准元素的值放在其右边,这样基准元素就位于了整个序列的中间,这个过程被称为分区(partition)。然后,对基准元素左右两边的子序列分别进行快速排序。递归这个过程,直到所有子序列都只包含一个元素,这时候整个序列就已经被排序完成。
以下是一个简洁易懂的快速排序的 Python 实现:
def quicksort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quicksort(left) + middle + quicksort(right)
这个函数首先检查输入数组的长度,如果长度小于或等于1,就直接返回数组,因为长度为1的数组已经是有序的。
然后函数选择中间的元素作为基准元素,然后通过列表推导式将数组分为三部分:小于基准的元素,等于基准的元素,和大于基准的元素。然后递归地对小于基准和大于基准的部分进行快速排序,最后返回排序后的结果。
注意这个实现虽然简洁,但并不是最优的。因为它需要额外的空间来存储左、中、右三个部分,而且需要遍历数组三次来分割三个部分。更高效的实现通常会在原地进行分区和排序,只需要O(log n)的额外空间(用于函数调用栈)并且只需要遍历一次数组。
以下是一个更优的快速排序实现,该实现在原地(in-place)进行排序,避免了额外的空间消耗,而且只需要一次遍历即可完成分区。
def quicksort(arr, low, high):
if low < high:
pi = partition(arr, low, high)
quicksort(arr, low, pi - 1)
quicksort(arr, pi + 1, high)
def partition(arr, low, high):
pivot = arr[high]
i = low - 1
for j in range(low, high):
if arr[j] <= pivot:
i += 1
arr[i], arr[j] = arr[j], arr[i]
arr[i+1], arr[high] = arr[high], arr[i+1]
return i + 1
# 调用方式:
arr = [10, 7, 8, 9, 1, 5]
n = len(arr)
quicksort(arr, 0, n-1)
print("Sorted array is:", arr)
上述代码中,quicksort函数接受一个待排序的数组和两个索引作为参数,分别代表待排序子数组的首尾。它首先检查待排序子数组的长度,如果长度大于1,就调用partition函数对其进行分区,然后递归地对基准元素左右两边的子数组进行快速排序。
partition函数则负责进行分区。它首先选择子数组的最后一个元素作为基准元素,然后从左到右遍历子数组,将小于或等于基准元素的值移到基准元素的左边。在遍历过程中,变量i始终指向了最后一个小于或等于基准元素的位置。遍历完之后,基准元素被放置在正确的位置(即i+1的位置),然后函数返回基准元素的位置。
注意这个实现只需要O(log n)的额外空间(用于函数调用栈),并且只需要一次遍历就可以完成分区。因此,它比前面提供的简洁版本更高效。
总结
- 需要掌握通过deque实现单调队列的方法
- heapq实现小顶堆的方法需要了解
- 复习了一下快排的内容