Python堆排序介绍与力扣三道堆相关题目分享

堆的定义

堆 是一种特别的二叉树,满足以下条件的二叉树,可以称之为 堆:

完全二叉树;
每一个节点的值都必须 大于等于或者小于等于 其孩子节点的值。
堆 具有以下的特点:

  • 可以在 O(logN)O(logN) 的时间复杂度内向 堆 中插入元素;
  • 可以在 O(logN)O(logN) 的时间复杂度内向 堆 中删除元素;
  • 可以在 O(1)O(1) 的时间复杂度内获取 堆 中的最大值或最小值。

堆的分类

堆 有两种类型:最大堆(大根堆) 和 最小堆(小根堆)。


最大堆:堆中每一个节点的值 都大于等于 其孩子节点的值。所以最大堆的特性是 堆顶元素(根节点)是堆中的最大值。

最小堆:堆中每一个节点的值 都小于等于 其孩子节点的值。所以最小堆的特性是 堆顶元素(根节点)是堆中的最小值。

堆的使用场景

用一句话来描述堆的使用场景就是:动态求极值。其中动态和极值两个条件缺一不可。即当我们遇到题目需要对一个数组进行持续的插入、删除,然后最终求top(N)问题时,不用想必然是堆排序问题。

Python堆模块的使用

在Python中,堆模块通过import heapq来导入,这里要说明下Python的堆都是小根堆,那么Python如何来计算大根堆呢?推荐的做法是将所有整数全部转化为负数,那么就实现了小根堆的操作。
heapq有两种方式创建堆

  1. 使用一个空列表,然后使用heapq.heappush()函数把值加入堆中
  2. 使用heap.heapify(list)转换列表成为堆结构
import heapq

# 方法1
nums = [2,5,1,7,9,10,3,4]
heap = []
for num in nums:
    heapq.heappush(heap, num)
while heap:
    print(heapq.heappop(heap))

# 方法2
nums = [2,5,1,7,9,10,3,4]
heapq.heapify(nums)
while heap:
    print(heap.pop())

堆在日常的使用频率上不是很多,如果仅为了刷题,那么只要了解这些内容就足够做题了。当然如果想细致了解堆的构成与实现,等闲下来了专门写一篇文章来详细讲述。

力扣堆题目

堆的题目总体难度在力扣的上算是比较困难的,但是出现和面试时的频率真的很低。53道题目只有3道简单、21道困难、29道中等,今天在这里给大家推荐三道题目,掌握这三道题,这类题型就差不多了...

  1. 1845.座位预约管理系统 是一道简单设计题目,掌握刚才说的动态、极值,那么这道题解起来简直不要太简单
  2. 1046.最后一块石头的重量 是一道考察大根堆的题目,如刚才所说python中我们需要把它转化为小根堆后再进行计算
  3. 313.超级丑数 这道题呢,算是稍难一点的综合题目,需要我们判断堆的重复值

下来分别看看这三道题目吧:

1845.座位预约管理系统

https://leetcode-cn.com/problems/seat-reservation-manager/solution/5731zuo-wei-yu-yue-guan-li-xi-tong-jian-tlmzu/

难度:中等

题目:

请你设计一个管理 n 个座位预约的系统,座位编号从 1 到 n 。

请你实现 SeatManager 类:

  • SeatManager(int n) 初始化一个 SeatManager 对象,它管理从 1 到 n 编号的 n 个座位。所有座位初始都是可预约的。
  • int reserve() 返回可以预约座位的 最小编号 ,此座位变为不可预约。
  • void unreserve(int seatNumber) 将给定编号 seatNumber 对应的座位变成可以预约。

提示:

  • 1 <= n <= 105
  • 1 <= seatNumber <= n
  • 每一次对reserve的调用,题目保证至少存在一个可以预约的座位。
  • 每一次对unreserve的调用,题目保证seatNumber在调用函数前都是被预约状态。
  • 对reserve 和unreserve的调用总共不超过105次。

示例:

示例 1:

输入:
["SeatManager", "reserve", "reserve", "unreserve", "reserve", "reserve", "reserve", "reserve", "unreserve"]
[[5], [], [], [2], [], [], [], [], [5]]
输出:
[null, 1, 2, null, 2, 3, 4, 5, null]

解释:
SeatManager seatManager = new SeatManager(5); // 初始化 SeatManager ,有 5 个座位。
seatManager.reserve();    // 所有座位都可以预约,所以返回最小编号的座位,也就是 1 。
seatManager.reserve();    // 可以预约的座位为 [2,3,4,5] ,返回最小编号的座位,也就是 2 。
seatManager.unreserve(2); // 将座位 2 变为可以预约,现在可预约的座位为 [2,3,4,5] 。
seatManager.reserve();    // 可以预约的座位为 [2,3,4,5] ,返回最小编号的座位,也就是 2 。
seatManager.reserve();    // 可以预约的座位为 [3,4,5] ,返回最小编号的座位,也就是 3 。
seatManager.reserve();    // 可以预约的座位为 [4,5] ,返回最小编号的座位,也就是 4 。
seatManager.reserve();    // 唯一可以预约的是座位 5 ,所以返回 5 。
seatManager.unreserve(5); // 将座位 5 变为可以预约,现在可预约的座位为 [5] 。

分析

类似这种简单类设计题,在日常面试还是比较多的。
这道题我们使用小根堆,解题简直不要太简单。

解题:

import heapq

class SeatManager:

    def __init__(self, n: int):
        self.ret = [i for i in range(1, n + 1)]

    def reserve(self):
        return heapq.heappop(self.ret)

    def unreserve(self, seatNumber):
        heapq.heappush(self.ret, seatNumber)

1046.最后一块石头的重量

https://leetcode-cn.com/problems/last-stone-weight/solution/1046zui-hou-yi-kuai-shi-tou-de-zhong-lia-1xub/

难度:简单

题目:

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

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

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

  • 1 <= stones.length <= 30
  • 1 <= stones[i] <= 1000

示例:

输入:[2,7,4,1,8,1]
输出:1
解释:
先选出 7 和 8,得到 1,所以数组转换为 [2,4,1,1,1],
再选出 2 和 4,得到 2,所以数组转换为 [2,1,1,1],
接着是 2 和 1,得到 1,所以数组转换为 [1,1,1],
最后选出 1 和 1,得到 0,最终数组转换为 [1],这就是最后剩下那块石头的重量。

分析

由于Python不支持大根堆,所以我们需要在预处理的时候,将所有数据转为负数用于适配小根堆。

循环判断的条件当然是堆内数据大于1,当为0和1时表示获取到结果,返回即可。

循环过程中,每次pop出堆内最小的两个数后,对两数根据题意进行比较:

  • 若两数相等,都碾碎
  • 若两数不相等,则将差值重新加入堆中

重复上面流程,最终即可获取结果。

解题:

import heapq

class Solution:
    def lastStoneWeight(self, stones):
        stones = [-i for i in stones]
        heapq.heapify(stones)
        while len(stones) > 1:
            one = heapq.heappop(stones)
            two = heapq.heappop(stones)
            if one != two:
                heapq.heappush(stones, one - two)
        return -stones[0] if stones else 0

313.超级丑数

https://leetcode-cn.com/problems/super-ugly-number/solution/313chao-ji-chou-shu-dui-pai-xu-si-lu-jia-v4iv/

难度:中等

题目:

编写一段程序来查找第 n 个超级丑数。

超级丑数是指其所有质因数都是长度为 k 的质数列表 primes 中的正整数。

说明:

  • 1 是任何给定 primes 的超级丑数。
  • 给定 primes 中的数字以升序排列。
  • 0 < k ≤ 100, 0 < n ≤ 106, 0 < primes[i] < 1000 。
  • 第 n 个超级丑数确保在 32 位有符整数范围内。

示例:

输入: n = 12, primes = [2,7,13,19]
输出: 32 
解释: 给定长度为 4 的质数列表 primes = [2,7,13,19],
    前 12 个超级丑数序列为:[1,2,4,7,8,13,14,16,19,26,28,32] 。

分析

我们可以动态维护一个当前最小的超级丑数。找到第一个,我们将其移除,再找下一个当前最小的超级丑数。
这样经过 n 轮,我们就得到了第 n 小的超级丑数。这种动态求极值的方式,符合堆排序的操作条件。

  1. 初始化ret = 1 为默认的返回值
  2. 我们通过for循环的方式每次找到一个最小值,默认为1。
  3. 最小值tmp出堆时,分别和primes中的每次元素p相乘后入堆。
  4. 此时,我们将tmp赋值给ret
  5. 如此反复3、4操作,直到取到第 n 个超级丑数。

在3 操作的时候,我们需要注意,由于在计算时可能存在相同值的场景,所以在出堆后,需要判断当前堆的最小值是否等于tmp,
如果等于,则需要持续出堆,一直到不相等为止。

解题:

import heapq

class Solution:
    def nthSuperUglyNumber(self, n, primes):
        hq = [1]
        ret = 1
        for i in range(n):
            tmp = heapq.heappop(hq)
            while hq and hq[0] == tmp:
                heapq.heappop(hq)
            for p in primes:
                heapq.heappush(hq, p * tmp)
            ret = tmp
        return ret

欢迎关注我的公众号: 清风Python,带你每日学习Python算法刷题的同时,了解更多python小知识。

我的个人博客:https://qingfengpython.cn

力扣解题合集:https://github.com/BreezePython/AlgorithmMarkdown

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342