力扣刷题记录&整理——(九)Heap/Priority Queue


前言

整理力扣刷题思路。

  • 语言:python
  • 题库:来自neetcode: link

一、预备知识

堆/优先队列

当谈到数据结构中的“堆”时,我们通常指的是优先队列。堆是一种特殊的二叉树,具有以下特性:

  1. 最小堆:每个节点的值都小于等于其子节点的值。
  2. 最大堆:每个节点的值都大于等于其子节点的值。

注意:在最小堆中,除了根节点外,其他节点的排序是无序的。

Python中的heapq模块提供了对堆的操作。让我们来详细解释一下:

  1. heappush(heap, item):将item的值加入堆中,保持堆的不变性。
  2. heappop(heap):弹出并返回堆中最小的元素,保持堆的不变性。如果堆为空,会抛出IndexError
  3. heappushpop(heap, item):将item放入堆中,然后弹出并返回堆中最小的元素。这个组合操作比先调用heappush()再调用heappop()更高效。
  4. heapify(x):将列表x转换成堆,原地,线性时间内。
  5. heapreplace(heap, item):弹出并返回堆中最小的一项,同时推入新的item。堆的大小不变。如果堆为空则引发IndexError

此外,heapq模块还提供了其他基于堆的通用目的函数,例如nlargest()nsmallest(),用于获取前N个最大或最小的元素。

基本示例:

import heapq

# 创建一个堆
heap = []
heapq.heappush(heap, 5)
heapq.heappush(heap, 3)

# 弹出最小的元素
min_element = heapq.heappop(heap)
print(min_element)  # 输出:3

总之,heapq模块是处理堆的强大工具,适用于许多场景,包括优先队列、堆排序等。


二、解题思路

1.堆

295.find-median-from-data-stream

中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。

例如 arr = [2,3,4] 的中位数是 3 。
例如 arr = [2,3] 的中位数是 (2 + 3) / 2 = 2.5 。
实现 MedianFinder 类:

MedianFinder() 初始化 MedianFinder 对象。

void addNum(int num) 将数据流中的整数 num 添加到数据结构中。

double findMedian() 返回到目前为止所有元素的中位数。与实际答案相差 10-5 以内的答案将被接受。

link

from heapq import *
class MedianFinder:
    def __init__(self):
        #维护一个最小堆,存储列表中较大的一半数
        self.big = []
        #维护一个最大堆,存储列表中较小的一半数
        #其实堆的形式仍然是最小堆,但每个值取其负数,使得对应的实际值是按最大堆排序
        self.sml = []

    def addNum(self, num: int) -> None:
        #长度相同时,需要把中位数挪到self.big的0位
        #先加入self.sml并弹出最小值(其负值为对应的较小的一半的最大值)
        #在入堆self.big,保持self.big的0位为队列最小值
        if len(self.big) == len(self.sml):
            tmp = heappushpop(self.sml, -num)
            heappush(self.big, -tmp)
            
        #长度不同(也即self.big多一位)时,需要增加self.sml也即较小的一半的长度
        #先入堆self.big并弹出最小值,此值小于较大的一半数,将其负值入堆self.sml
        else:
            tmp = heappushpop(self.big, num)
            heappush(self.sml, -tmp)

    def findMedian(self) -> float:
        if len(self.big) == len(self.sml):
            return (self.big[0] - self.sml[0])/2
        else:
            return self.big[0]


# Your MedianFinder object will be instantiated and called as such:
# obj = MedianFinder()
# obj.addNum(num)
# param_2 = obj.findMedian()

703.kth-largest-element-in-a-stream

设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。

请实现 KthLargest 类:

KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。
link

from heapq import *

class KthLargest:

    def __init__(self, k: int, nums: List[int]):
        self.k = k
        self.hp = []
        for num in nums:
            heappush(self.hp, num)

    def add(self, val: int) -> int:
        heappush(self.hp, val)
        #堆只保证根节点是最小值,因此我们只保留最大的k个数
        while len(self.hp)>self.k:
            heappop(self.hp)
        return self.hp[0]


# Your KthLargest object will be instantiated and called as such:
# obj = KthLargest(k, nums)
# param_1 = obj.add(val)

1046.last-stone-weight

有一堆石头,每块石头的重量都是正整数。

每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0。
link

from heapq import *

class Solution:
    def lastStoneWeight(self, stones: List[int]) -> int:
        hp = [-stone for stone in stones]
        heapify(hp)

        while len(hp)>1:
            t1 = heappop(hp)
            t2 = heappop(hp)
            if t1 != t2:
                heappush(hp, t1-t2)
        return -hp[0] if hp else 0

973.k-closest-points-to-origin

给定一个数组 points ,其中 points[i] = [xi, yi] 表示 X-Y 平面上的一个点,并且是一个整数 k ,返回离原点 (0,0) 最近的 k 个点。

这里,平面上两点之间的距离是 欧几里德距离( √(x1 - x2)2 + (y1 - y2)2 )。

你可以按 任何顺序 返回答案。除了点坐标的顺序之外,答案 确保 是 唯一 的。

link

from heapq import *
class Solution:
    def kClosest(self, points: List[List[int]], k: int) -> List[List[int]]:
        hp = []
        for point in points:
            dist = point[0]**2 + point[1]**2
            heappush(hp,(dist,point))
        
        ans = []
        for _ in range(k):
            #堆会按照元组的第一个元素(也就是距离)的大小弹出
            ans.append(heappop(hp)[1])
        return ans

355.design-twitter

设计一个简化版的推特(Twitter),可以让用户实现发送推文,关注/取消关注其他用户,能够看见关注人(包括自己)的最近 10 条推文。

实现 Twitter 类:

  • Twitter() 初始化简易版推特对象
  • void postTweet(int userId, int tweetId) 根据给定的 tweetId 和 userId 创建一条新推文。每次调用此函数都会使用一个不同的 tweetId 。
  • List getNewsFeed(int userId) 检索当前用户新闻推送中最近 10 条推文的 ID 。新闻推送中的每一项都必须是由用户关注的人或者是用户自己发布的推文。推文必须 按照时间顺序由最近到最远排序 。
  • void follow(int followerId, int followeeId) ID 为 followerId 的用户开始关注 ID 为 followeeId 的用户。
  • void unfollow(int followerId, int followeeId) ID 为 followerId 的用户不再关注 ID 为 followeeId 的用户。
    link
from heapq import *

class Twitter:

    def __init__(self):
        self.user = {} #用户字典
        self.Tid = 0 #记录发推的顺序
    
    def addNewUser(self, userID):
        F = {userID}
        T = collections.deque(maxlen=10)
        #新用户默认关注自己
        #每个key(用户)对应的value也是字典,分别记录其关注的人和其自己发的推
        self.user[userID] = {'followee':F, 'tweet':T}

    def postTweet(self, userId: int, tweetId: int) -> None:
        if userId not in self.user:
            self.addNewUser(userId)
        
        #记录当前用户发的推:(推的顺序,推文ID)
        self.user[userId]['tweet'].append((self.Tid,tweetId))
        self.Tid += 1

    def getNewsFeed(self, userId: int) -> List[int]:
        if userId not in self.user:
            return []

        alltwt = []
        #获取当前用户关注的所有用户发的所有推
        for uid in self.user[userId]['followee']:
            alltwt.extend(list(self.user[uid]['tweet']))
        #取发推顺序最大(也即最近)的十条
        t10 = nlargest(10, alltwt)
        return [t[1] for t in t10]


    def follow(self, followerId: int, followeeId: int) -> None:
        if followerId not in self.user:
            self.addNewUser(followerId)
        if followeeId not in self.user:
            self.addNewUser(followeeId)
        self.user[followerId]['followee'].add(followeeId)


    def unfollow(self, followerId: int, followeeId: int) -> None:
        if followerId in self.user and followeeId in self.user and followerId!=followeeId:
            self.user[followerId]['followee'].discard(followeeId)


# Your Twitter object will be instantiated and called as such:
# obj = Twitter()
# obj.postTweet(userId,tweetId)
# param_2 = obj.getNewsFeed(userId)
# obj.follow(followerId,followeeId)
# obj.unfollow(followerId,followeeId)

参考:link

  • 12
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值