代码随想录算法训练营第十三天|239. 滑动窗口最大值、347.前 K 个高频元素

239. 滑动窗口最大值 - 🔗

讲解 - 🔗
💡只想到了要用暴力解法,但是最后超出时间限制了。我觉得最主要的难点在于如何使最大值一直处于队列最左侧,这样子每一次popleft得到的结果就一定是最大值。

方法一:超出时间限制
from collections import deque
class Solution:
    def maxSlidingWindow(self, nums: list[int], k: int) -> list[int]:
        que = deque()
        max_record = float('-inf') # 负无穷
        res = []
        i = 0
        while i < len(nums):
            if len(que) < k:
                que.append(nums[i])
                max_record = nums[i] if max_record < nums[i] else max_record
                i += 1
            else:
                res.append(max_record)
                que.popleft() # 移除原k个元素的第一个,模拟向后移的过程
                max_record = max(que) if que else float('-inf')
        res.append(max_record)
        return res
方法二:参考代码

💡每向右移动一位,就相当于popleft一个元素,再append一个元素,然后输出最大值,可以视为一次操作。这个解法就直接把一整个操作包装成了一个类MyQueue。当MyQueue下面定义三个方法,其中最重要的是push()方法。它的主要意图就是确保每输入一次元素,都跟前序元素做比较,确保插入元素一定小雨前序元素,否则就将前序元素移除。这样子就可以确保队列的最左侧一定是当前k个元素中的最大值,那么当下一步进行getMaxVAlue()的时候就可以直接return self.que[0]

from collections import deque
class MyQueue:
    def __init__(self):
        self.que = deque()
    def pop(self, val: int) -> None:
        if self.que and self.que[0] == val:
            self.que.popleft()

    def push(self, val: int) -> None:
        while self.que and self.que[-1] < val: # que不为空时,que[-1]才有意义
            self.que.pop()
        self.que.append(val)

    def getMaxValue(self) -> int:
        return self.que[0]

class Solution:
    def maxSlidingWindow(self, nums: list[int], k: int) -> list[int]:
        res = []
        que = MyQueue()

        # 前k个元素
        for i in range(k):
            que.push(nums[i])
        res.append(que.getMaxValue())

        # 剩余元素
        for i in range(k, len(nums)):
            que.pop(nums[i - k])
            que.push(nums[i])
            res.append(que.getMaxValue())

        return res

347.前 K 个高频元素 - 🔗

讲解 - 🔗
💡之前没有学过堆,思路只停留在用dict存储每个月素出现的次数就卡住了。因为想不到可以用什么来有序地遍历或者存储dict中的元素和值。看了解析之后,才发现堆这种数据结构的优势,正好可以用于解决此类问题。因为堆在插入元素时会自动维护堆的性质,确保堆仍然保持有序。具体来说,对于小顶堆(最小堆),插入新元素后,堆会自动进行调整,将新元素放到合适的位置,以满足小顶堆的性质,即每个节点都小于或等于其子节点。

一、堆介绍:

在计算机科学中,堆(Heap)是一种特殊的数据结构,它是一种树形结构,通常用于实现优先队列。堆具有以下特性:

  1. 完全二叉树: 堆通常是一个完全二叉树,这意味着除了最后一层,其他所有层的节点都被完全填充,而且最后一层的节点都尽可能靠左排列。
  2. 堆属性:最小堆中,父节点的值小于或等于其子节点的值;在最大堆中,父节点的值大于或等于其子节点的值。这就是所谓的"堆属性"。

二、用python实现堆:

在 Python 中,堆通常通过 heapq 模块来实现。heapq 提供了一些堆操作的函数,用于在列表上实现堆的行为。以下是一些常见的堆操作:

1. 创建堆:
heapq.heapify(iterable): 将可迭代对象(如列表)转换为堆。
2. 堆的基本操作:
heapq.heappush(heap, elem): 将元素添加到堆中。
heapq.heappop(heap): 弹出并返回堆中的最小元素。

3. 获取堆的最小元素(不弹出):
heapq.nsmallest(n, iterable): 返回可迭代对象中的前 n 个最小元素。
heapq.nlargest(n, iterable): 返回可迭代对象中的前 n 个最大元素。
4. 合并堆:
heapq.merge(*iterables): 合并多个有序序列,返回一个排序后的新序列。
5. 堆排序:
heapq.heapsort(iterable): 在-place 对可迭代对象进行堆排序。
6. 获取堆的最小值(不弹出):
heapq.peek(heap): 返回堆中的最小元素,但不弹出。

import heapq

# 创建一个空堆
heap = []

# 将元素加入堆
heapq.heappush(heap, 4)
heapq.heappush(heap, 1)
heapq.heappush(heap, 7)

# 弹出堆中最小元素
min_element = heapq.heappop(heap)

# 获取堆中最小元素,但不弹出
min_element = heap[0]

# 将列表转换为堆
heapq.heapify(heap)

# 从堆中删除指定元素
heapq.heappop(heap)

# 合并多个有序序列,返回一个排序后的新序列
merged = heapq.merge([1, 3, 5], [2, 4, 6])

请注意,heapq 默认实现的是最小堆。如果需要最大堆,可以通过将元素取负数的方式实现。

# 最大堆
max_heap = [-x for x in heap]
#时间复杂度:O(nlogk)
#空间复杂度:O(n)
import heapq
class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        # 用字典要统计元素出现频率
        map_ = {} 
        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(): # key为元素值,frep为元素出现频率
            heapq.heappush(pri_que, (freq, key)) # 插入(freq, key)元素,自动比较freq的大小
            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

值得注意的是heapq.heappush(pri_que, (freq, key)),在heapq.haedpush()函数的第二个参数中传入了一个元组(用()表示)。在小顶堆中,元组的比较是从左到右按照元组元素的顺序进行的。因此,由于这道题目要比较的是频率的大小,而非元素值的大小,所以把freq放在key前面,即(freq, key),而非(key, freq)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值