zoukankan      html  css  js  c++  java
  • 【Leetcode 堆、快速选择、Top-K问题 BFPRT】有序矩阵中第K小的元素(378)

    题目

    给定一个 n x n 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第k小的元素。
    请注意,它是排序后的第k小元素,而不是第k个元素。

    示例:

    matrix = [
       [ 1,  5,  9],
       [10, 11, 13],
       [12, 13, 15]
    ],
    k = 8,
    
    返回 13。
    

    说明:
    你可以假设 k 的值永远是有效的, 1 ≤ k ≤ n2 。

    解答

    这个问题和Leetcode 215笔记非常相似,可以用相同的几种思路解决掉。其中BFPRT时间复杂度O(N)

    但这个题的输入是一个有序的矩阵,应该是有更好的办法吧!?找一圈没找到,有时间再来看。

    思路:
    1,全部收入列表,排序,取值。O(N·log(N))
    2,维护一个大小为 k 的堆,元素大于等于堆顶负数入堆,堆顶就是第k小。O(N·log(k))
    3,快速选择。最好O(N),最坏O(N^2)
    4,BFPRT。O(N)

    注:N表示元素个数,即n^2个

    通过代码如下:

    import random
    from heapq import *
    
    class Solution:
        # 排序
        # def kthSmallest(self, matrix: List[List[int]], k: int) -> int:
        #     l = []
        #     for m in matrix:
        #         l.extend(m)
        #     return sorted(l)[k-1]
    
        # 快速选择
        # def kthSmallest(self, matrix, k):
        #     nums = []
        #     for m in matrix:
        #         nums.extend(m)
    
        #     def partition(left, right, base):
        #         temp = nums[base]
        #         nums[base], nums[right] = nums[right], nums[base]  # 基准和末尾元素互换
    
        #         max_index = left
        #         for i in range(left, right):  # 把所有小于基准的移到左边
        #             if nums[i] < temp:
        #                 nums[max_index], nums[i] = nums[i], nums[max_index]
        #                 max_index += 1
    
        #         nums[right], nums[max_index] = nums[max_index], nums[right]  # 基准归位
        #         return max_index
    
        #     def select(left, right, k_smallest):
        #         """在 nums[left, right] 找第k小的元素"""
        #         if left == right:  # 递归终止条件
        #             return nums[left]
        #         pivot_index = random.randint(left, right)  # 随机选择基准(比固定选第一个要好)
        #         base_index = partition(left, right, pivot_index)  # 选第一个(left)为基准,并归位。
        #         if base_index == k_smallest:  # 判断目前已归位的基准,是不是第k_smallest位
        #             return nums[k_smallest]
        #         elif k_smallest < base_index:  # go to 左半部分
        #             return select(left, base_index - 1, k_smallest)
        #         else:  # go to 右半部分
        #             return select(base_index + 1, right, k_smallest)
    
        #     return select(0, len(nums) - 1, k-1)  # 第k大,是第n-k小
    
        # 堆
        # def kthSmallest(self, matrix, k):
        #     nums = []
        #     for m in matrix:
        #         nums.extend(m)
        #     hq = []
        #     for x in nums:
        #         if len(hq) < k:
        #             heappush(hq, -x)
        #         elif -x >= hq[0]:
        #             heapreplace(hq, -x)
        #     return -heappop(hq)
    
        # BFPRT
        def kthSmallest(self, matrix: List[List[int]], k: int) -> int:
            nums = []
            for m in matrix:
                nums.extend(m)
    
            def getmedian(lis):
                """返回序列lis中位数,在BFPRT中就是求每5个数小组的中位数"""
                begin = 0
                end = len(lis)-1
    
                sum = begin+end
                mid = sum//2 + sum % 2  # 这个地方加上sum%2是为了确保偶数个数时我们求的是中间两个数的后一个
                return sorted(lis)[mid]
    
            def BFPRT(nums, left, right):
                """分成每5个数一个小组,并求出每个小组内的中位数"""
                num = right-left+1
                offset = 0 if num % 5 == 0 else 1  # 最后如果剩余的数不足5个,我们也将其分成一个小组,和前面同等对待
                groups = num//5 + offset
                median = []  # 中位数数组
                for i in range(groups):
                    begin = left+i*5
                    end = begin + 4
                    Median = getmedian(nums[begin:min(end, right)+1])
                    median.append(Median)
                return getmedian(median)
    
            def partition(nums, left, right, base):
                """在 nums[left, right] 将基准base归位"""
                temp = nums[base]
                nums[base], nums[right] = nums[right], nums[base]  # 基准和末尾元素互换
    
                max_index = left
                for i in range(left, right):  # 把所有小于基准的移到左边
                    if nums[i] <= temp:  # 要等于啊!这里好坑的说.. 否则通不过[3, 3, 3, 3, 4, 3, 3, 3, 3]  k = 1
                        nums[max_index], nums[i] = nums[i], nums[max_index]
                        max_index += 1
                nums[right], nums[max_index] = nums[max_index], nums[right]  # 基准归位
                return max_index
    
            def select(nums, left, right, k_smallest):
                """在 nums[left, right] 找第k小的元素"""
                if left == right:  # 递归终止条件
                    return nums[left]
                # pivot_index = random.randint(left, right)
                base = BFPRT(nums, left, right)
                base_index = partition(nums, left, right, nums.index(base))  # 选base为基准,并归位。
                if base_index == k_smallest:  # 判断目前已归位的基准,是不是第k_smallest位
                    return nums[k_smallest]
                elif k_smallest < base_index:  # 递归左半部分
                    return select(nums, left, base_index - 1, k_smallest)
                else:  # 递归右半部分
                    return select(nums, base_index + 1, right, k_smallest)
            return select(nums, 0, len(nums) - 1, k-1)  # 第k大,是第n-k小
    
  • 相关阅读:
    跟结束进程相关的那些信号
    tcpdump使用示例
    Linux在bash history当中添加timestamp
    CentOS中在/etc/rc.local添加开机自启动项启动失败
    CentOS配置通过DHCP的方式动态获取IP
    CentOS桌面安装
    MySQL二进制安装
    对okhttp参数的一些思考
    依赖倒置原则(DIP)
    Liskov替换原则(LSP)
  • 原文地址:https://www.cnblogs.com/ldy-miss/p/12039917.html
Copyright © 2011-2022 走看看