【Leetcode Top-K问题 BFPRT】第三大的数(414)

题目

给定一个非空数组,返回此数组中第三大的数。如果不存在,则返回数组中最大的数。要求算法时间复杂度必须是O(n)

示例 1:

输入: [3, 2, 1]
输出: 1
解释: 第三大的数是 1.

示例 2:

输入: [1, 2]
输出: 2
解释: 第三大的数不存在, 所以返回最大的数 2 .

示例 3:

输入: [2, 2, 3, 1]
输出: 1
解释: 注意,要求返回第三大的数,是指第三大且唯一出现的数。存在两个值为2的数,它们都排第二。

解答

思路:
1,题目要求时间复杂度必须是O(n),那么排序肯定是不行了,由于只是求第三大,那么可以先求出第一大,再求出第二大,再求第三大,总的时间复杂度O(N)。
2,BFPRT,专门解决TOP-K问题,但是因为序列中可能有重复值,所以在进行BFPRT之前,先去重,总的时间复杂度O(N)。

(2020.1.7更)
3,堆,大小为k的小顶堆,k=3。Time: N·log(k) = O(N), Space: O(1)
4,二叉排序树,首先构建一颗二叉排序树,中序遍历得出从小到大的序列,取第K大。构建二叉排序树平均log(N),最差O(N),查找第K大O(N)
5,AVL树,优化的二叉排序树,优化了构建二叉树的时间复杂度,最差log(N),中序遍历查找第K大O(N)

通过代码如下:
方法一

class Solution:
    def thirdMax(self, nums: List[int]) -> int:
        l = list(set(nums))
        max_list = []
        sum = min(3, len(l))

        while sum:
            max = -float('inf')  # 负无穷
            for x in l:
                if x > max and x not in max_list:
                    max = x
            if max not in max_list:
                # l.remove(max)  remove时间复杂度为O(n)
                del l[l.index(max)]
                max_list.append(max)
            sum -= 1
        return max_list[-1] if len(max_list)>=3 else max_list[0]

BFPRT

class Solution:
    def thirdMax(self, nums) -> int:
        nums = list(set(nums))
        if len(nums) < 3:
            return max(nums)

        return self.BFPRT(nums, 0, len(nums) - 1, len(nums)-3)

    def BFPRT(self, nums, left, right, K):
        """BFPRT算法"""
        if left == right:
            return nums[left]  # 不是排序,这要返回值

        base = self.medianOfMedians(nums, left, right)  # 找基准
        base_index = self.partition(nums, left, right, nums.index(base))  # 基准归位

        if base_index == K:
            return nums[base_index]
        elif base_index > K:
            return self.BFPRT(nums, left, base_index - 1, K)  # 递归左边。不是快排,这要返回值
        else:
            return self.BFPRT(nums, base_index + 1, right, K)  # 递归右边

    def medianOfMedians(self, nums, left, right):
        """找中位数基准"""
        length = right - left + 1
        offset = 0 if length % 5 == 0 else 1  # 最后不够5个的算一组
        groups = length // 5 + offset

        medians = []
        for i in range(groups):
            start = left + i * 5
            end = start + 4
            medians.append(self.get_median(nums[start: min(end, right) + 1]))
        return self.BFPRT(medians, 0, groups - 1,
                          groups // 2)  # 这里递归BFPRT,保证得到的是“准确中位数”(30% ~ 70%); 而非“近似中位数” 防止时间复杂度退化

    def get_median(self, nums):
        """找5个数的中位数"""
        return sorted(nums)[len(nums) // 2]  # 常数级别

    def partition(self, nums, left, right, base):
        """常规partition"""
        if left >= right:
            return
        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[i], nums[max_index] = nums[max_index], nums[i]
                max_index += 1
        nums[max_index], nums[right] = nums[right], nums[max_index]
        return max_index
posted @ 2019-12-13 15:19  961897  阅读(412)  评论(0编辑  收藏  举报