日常系列LeetCode《11·堆和优先队列篇》

Posted 常某某的好奇心

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了日常系列LeetCode《11·堆和优先队列篇》相关的知识,希望对你有一定的参考价值。

数据规模->时间复杂度

<=10^4 😮(n^2)
<=10^7:o(nlogn)
<=10^8:o(n)
10^8<=:o(logn),o(1)

总结

有限容量的小顶堆:最大k个元素
有限容量的大顶堆:最小k个元素







堆化时间复杂度:o(n)

lc 1046:最后一块石头的重量
https://leetcode.cn/problems/last-stone-weight/
提示:
1 <= stones.length <= 30
1 <= stones[i] <= 1000

#方案一:排序
class Solution:
    def lastStoneWeight(self, stones: List[int]) -> int:
        n=len(stones)
        if n == 1: return stones[0]
        #o(n * nlogn)
        for i in range(n-1): #比较次数
            #每次都进行排序
            stones.sort(reverse=False)
            #
            if stones[n-2]==0:break #优化:例如000001
            stones[n-1]=stones[n-1]-stones[n-2]
            stones[n-2]=0
        return stones[n-1]

#方案二:大顶堆
import heapq
class Solution:
    def lastStoneWeight(self, stones: List[int]) -> int:
        n=len(stones)
        if n == 1: return stones[0]
        #o(n),o(nlogn)
        s=[-x for x in stones]
        heapq.heapify(s)#大堆化
        while len(s)>1:
            y=heapq.heappop(s)
            x=heapq.heappop(s)
            if x!=y:heapq.heappush(s,y-x)
        #
        return -s[0] if s else 0         

lc 215 【剑指 076】:数组中的第 K 个
https://leetcode.cn/problems/kth-largest-element-in-an-array/
提示:
1 <= k <= nums.length <= 10^5
-10^4 <= nums[i] <= 10^4
你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。


#方案一:排序(不满足)
class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        #o(logn)/o(n),o(nlogn)
        nums.sort()
        return nums[len(nums)-k]
#方案二:小顶堆 + 大顶堆(不满足)
import heapq
class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        n=len(nums)
        #o(k)/o(n-k),o(nlogk)
        if k<=n-k+1: #小顶堆
            pq=nums[:k]
            heapq.heapify(pq)
            for num in nums[k:]:#key:放k+1元素,更新后,去头(‘第k-1大’)
                heapq.heappush(pq,num)
                heapq.heappop(pq)
            #
            return pq[0]
        else:#大顶堆优化:当k比较大的时候——第k大——>第n-k+1小
            pq=[-x for x in nums[:n-k+1]]
            heapq.heapify(pq)
            for num in nums[n-k+1:]:
                heapq.heappush(pq,-num)#key:-
                heapq.heappop(pq)
            return -pq[0]
       
#方案三:二路快排 
import random
class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        n=len(nums)
        #o(1),o(n)
        left,right=0,n-1
        target=n-k
        while True:
            pivot=self.pivot(nums,left,right)
            if pivot==target:return nums[pivot]
            elif pivot>target:right=pivot-1
            else:left=pivot+1

    def pivot(self,nums,lo,hi):
        i=random.randint(lo,hi)
        nums[i],nums[hi]=nums[hi],nums[i]
        pivot=nums[hi]
        less=great=lo
        while great<=hi-1:
            if nums[great]<pivot:
                nums[less],nums[great]=nums[great],nums[less]
                less+=1
            great+=1
        nums[less],nums[hi]=nums[hi],nums[less]
        return less       

lc 347【剑指 060】【top100】:前 K 个高频元素
https://leetcode.cn/problems/top-k-frequent-elements/
提示:
1 <= nums.length <= 105
k 的取值范围是 [1, 数组中不相同的元素的个数]
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的
进阶:
你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。

#方案一:小顶堆
import collections
import heapq
class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        #o(n),o(nlogk)
        knums=[]
        count=collections.Counter(nums)#MAP     
        for num,cnt in count.items(): #dict结构
            heapq.heappush(knums,(cnt,num))
            if len(knums)>k:heapq.heappop(knums) #维护k个
        return [x[1] for x in knums]      
         
#方案二:二路快排
import collections
import random
class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        #o(n)
        count=collections.Counter(nums)#MAP     
        nums=[(cnt,num) for num,cnt in count.items()] #dict结构
        #o(n)
        left,right=0,len(nums)-1
        target=len(nums)-k #升序
        while True:
            pivot=self.pivot(nums,left,right)
            if pivot == target:
                break
            elif pivot < target:
                left = pivot+1
            else:
                right = pivot - 1
        return [x[1] for x in nums[-k:]] #区别:nums[-k:][1]为其中第二个dict
    
    def pivot(self,nums,lo,hi):
        i=random.randint(lo,hi)
        nums[i], nums[hi] = nums[hi], nums[i]
        pivot = nums[hi][0] #key
        less = great = lo
        while great <= hi - 1:
            if nums[great][0] < pivot:
                nums[less], nums[great] = nums[great], nums[less]
                less += 1
            great += 1
        nums[less], nums[hi] = nums[hi], nums[less]
        return less

lc 973:最接近原点的 K 个点
https://leetcode.cn/problems/k-closest-points-to-origin/
提示:
1 <= k <= points.length <= 10^4
-10^4 < xi, yi < 10^4

#方案一:大顶堆
import heapq
class Solution:
    def kClosest(self, points: List[List[int]], k: int) -> List[List[int]]:
        #o(k)
        pq=[(-x**2-y**2,i) for i,(x,y) in enumerate(points[:k])] #key:i<->(x,y)
        heapq.heapify(pq)#大顶堆
        #o(nlogk)
        for i in range(k,len(points)):
            x,y=points[i]
            dist=-x**2-y**2
            heapq.heappushpop(pq,(dist,i))
        return [points[i] for (_,i) in pq]
        
#方案二:二路快排
import heapq
class Solution:
    def kClosest(self, points: List[List[int]], k: int) -> List[List[int]]:
        #o(1),o(n)
        left, right = 0, len(points) - 1
        target = len(points) - k 
        while True:
            pivot = self.pivot(points, left, right)
            if pivot == target:
                break
            elif pivot < target:
                left = pivot + 1
            else:
                right = pivot - 1
        return [x for x in points[-k:]]

    def pivot(self, nums, lo, hi) -> None:
        i = random.randint(lo, hi)
        nums[i], nums[hi] = nums[hi], nums[i]
        pivot = nums[hi][0] ** 2 + nums[hi][1] ** 2 #key
        less = great = lo
        while great <= hi - 1:
            dist = nums[great][0] ** 2 + nums[great][1] ** 2
            # 降序排列
            if dist > pivot:
                nums[less], nums[great] = nums[great], nums[less]
                less += 1
            great += 1
        nums[less], nums[hi] = nums[hi], nums[less]
        return less   

lc 703 【剑指 059】:数据流中的第 K 大
https://leetcode.cn/problems/kth-largest-element-in-a-stream/
提示:
1 <= k <= 10^4
0 <= nums.length <= 10^4
-10^4 <= nums[i] <= 10^4
-10^4 <= val <= 10^4
最多调用 add 方法 104 次
题目数据保证,在查找第 k 大元素时,数组中至少有 k 个元素

#方案一:普通排序
class KthLargest:
    def __init__(self, k: int, nums: List[int]):
        self.data=nums #动态数组
        self.k=k
    #o(n)/o(logn),o(nlogn)
    def add(self, val: int) -> int:
        self.data.append(val)
        self.data.sort()
        return self.data[len(self.data)-self.k]
# Your KthLargest object will be instantiated and called as such:
# obj = KthLargest(k, nums)
# param_1 = obj.add(val)

#方案二:插入排序(超时)
class KthLargest:

    def __init__(self, k: int, nums: List[int]):
        self.data=nums #动态数组
        self.k=k
        self.data.sort()
    #o(n)/o(logn),o(nlogn)
    def add(self, val: int) -> int:
        n=len(self.data)
        self.data.append(val)#扩容n+1
        #
        j=n 
        while j>=1 and val<self.data[j-1]:
            self.data[j]=self.data[j-1]
            j-=1
        self.data[j]=val
        #
        return self.data[n+1-self.k]
# Your KthLargest object will be instantiated and called as such:
# obj = KthLargest(k, nums)
# param_1 = obj.add(val)

#方案三:小顶堆
import heapq
class KthLargest:

    def __init__(self, k: int, nums: List[int]): 
        self.k=k
        self.pq=[] #注:位置(动态维护)
        #o(k),o(nlogk)
        for num in nums:
            self.add(num)
    def add(self, val: int) -> int:
        if len(self.pq)<self.k:
            heapq.heappush(self.pq,val)
        else:
            heapq.heappushpop(self.pq,val) 
        return self.pq[0]
        
# Your KthLargest object will be instantiated and called as such:
# obj = KthLargest(k, nums)
# param_1 = obj.add(val)

lc 295【剑指 41】:数据流的中位数
https://leetcode.cn/problems/find-median-from-data-stream/
进阶:
如果数据流中所有整数都在 0 到 100 范围内,你将如何优化你的算法?
如果数据流中 99% 的整数都在 0 到 100 范围内,你将如何优化你的算法?



#方案一:普通排序
class MedianFinder:

    def __init__(self):
        self.data=[]

    def addNum(self, num: int) -> None:
        self.data.append(num)
    #o(n)/o(logn),o(nlogn)
    def findMedian(self) -> float:
        self.data.sort()
        n=len(self.data)
        if n%2==1:
            return self.data[n//2]
        else:
            return (self.data[n//2-1]+self.data[n//2])/2
            
# Your MedianFinder object will be instantiated and called as such:
# obj = MedianFinder()
# obj.addNum(num)
# param_2 = obj.findMedian()

#方案二:插入排序(超时)
class MedianFinder:
    def __init__(self):
        self.data=[]
    #o(1),o(n)
    def addNum(self, num: int) -> None:
        n = len(self.data)
        self.data.append(num)
        j = n
        while j >=1 and num < self.data[j - 1]:
            self.data[j] = self.data[j - 1]
            j -= 1
        self.data[j] = num

    def findMedian(self) -> float:
        n=len(self.data)
        if n%2==1:
            return self.data[n//2]
        else:
            return (self.data[n//2-1]+self.data[n//2])/2
                
# Your MedianFinder object will be instantiated and called as such:
# obj = MedianFinder()
# obj.addNum(num)
# param_2 = obj.findMedian()

#方案三:大顶堆+小顶堆
class MedianFinder:

    def __init__(self):
        #key:len(大顶堆)=len(小顶堆)/len(小顶堆)+1
        self.min_heap = []
        self.max_heap = []
    
    #o(logn)
    def addNum(self, num: int) -> None:
        #第一轮
        if not len(self.max_heap):
            heapq.heappush(self.max_heap,以上是关于日常系列LeetCode《11·堆和优先队列篇》的主要内容,如果未能解决你的问题,请参考以下文章

堆和优先级队列3:不泡妹子都要会的LeetCode7道题之一

堆和优先级队列4:不泡妹子都要会的LeetCode7道题之二

升序堆和降序堆(优先队列) 洛谷1801

堆和优先级队列2:java实现堆和优先级队列

堆和优先队列

堆和优先队列