【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