运用最小堆(更新至8.24)

本文介绍了如何使用Python的最小堆(即小根堆)解决取最小k个数问题,以及如何合并已排序链表。通过实例展示了如何通过heappush和heappop操作实现大根堆转小根堆的应用,并涉及到了链表合并的两种方法:排列+归并和小根堆优先输出。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Python语言中的堆为最小堆,所以用Python刷题都是在最小堆的基础上。

先说一个结论:最大堆解决取最小的问题,最小堆解决取最大的问题

4.22 找最小的k个数

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

例如
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]

根据之前的结论,取最小用大根堆。那具体的思路是:
维护长度为k的大根堆,新的数和堆顶元素比较,如果小于堆顶,则堆顶出来,新的元素进去,这样的k个元素就是最小的。

其实和堆顶元素的比较,就是和当前第k小的元素(守门员)比较的过程,你赢了你就有资格进去,守门员爬。

而Python的堆是小根堆,所以解题的时候需要取相反数,问题转化为用小根堆找相反数arr中最大的k个值。
代码如下:

class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        if k == 0:
            return list()
		#初始化长度为k的堆
        hp = [-x for x in arr[:k]]
        heapq.heapify(hp)
        for i in range(k, len(arr)):
            if -hp[0] > arr[i]:
                heapq.heappop(hp)
                heapq.heappush(hp, -arr[i])
        ans = [-x for x in hp]
        return ans

6.29 合并升序链表

关于堆中复杂元素的比较问题,默认是取第一个元素,详情见:
Python heapq 自定义比较器
给你一个链表数组(不知道多少个),每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
例如:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6

这道题是困难,但只是麻烦。有点像那种综合题,需要用到多个知识点。
先来第一种解法,用到了排列两个链表数组 + 归并的解。就是把问题拆分嘛,排列多个我不会,但是排列两个我会,这里注意一下归并排序的函数,有点难想- -

def merge(lists: List[Optional[ListNode]], l, r):
	if (l == r):
		return lists[l]
    if (l > r):
        return None
    else:
        mid =  (l + r) // 2
        return mergeTwoLists(merge(lists, l, mid), merge(lists, mid + 1, r))

第二种就利用到了小根堆,每次输出最小的一个元素。输出完再补元素进堆(如果对应链表有的话),注意一个问题,链表结点不能直接进小根堆,得变成元祖(val,id)进去,这样默认比的是val。根据id数判断是哪一个链表的结点。

class Solution:
    def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
        hp, K, res = [], len(lists), ListNode()
        head = res
        for i in range(K):
            if (lists[i]):
                heapq.heappush(hp, (lists[i].val, i))
        while (hp):
            val, idx = heapq.heappop(hp)
            head.next = ListNode(val)
            head = head.next
            lists[idx] = lists[idx].next 
            if(lists[idx]):
                heapq.heappush(hp, (lists[idx].val, idx)) #如果lists[idx]后面还有元素,后面的元素继续进堆!
        return res.next

求第k个最小的子序列和

首先考虑本题的简化问题:给定 n 个非负数 a 1 , a 2 , ⋯   , a n a_1, a_2, \cdots, a_n a1,a2,,an ,求第 k 个最小的子序列和。

我们先把所有数从小到大排序,记 (s, i) 表示一个总和为 s,以第 i 个元素结尾的子序列。

用一个小根堆维护 ( s , i ) (s, i) (s,i),一开始堆中只有一个元素 ( a 1 , 1 ) (a_1, 1) (a1,1)
当我们取出堆顶元素 (s, i)(s,i) 时,我们可以进行以下操作:
①把 a i + 1 a_{i + 1} ai+1接到这个子序列的后面形成新的子序列。
②把子序列中的 a i a_i ai 直接换成 a i + 1 a_{i + 1} ai+1
为什么只换最后一个?因为最后一个是【比较大的,换成大的】不会跳过别的子序列。​

第 (k - 1)次取出的 (s, i)中的 s 就是答案(k = 1 时答案为空集之和,也就是 0)。

这个做法的正确性基于以下事实:
这种方法能不重不漏地生成所有子序列。
每次放进去的数不小于拿出来的数。
可以动笔试一下[1,2,4,8],就能大概理解了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值