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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值