【BFPRT】数组中出现次数超过一半的数字
题目
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。
如果不存在则输出0。
解答
1, 一个数在序列中出现次数超过一半,那这个数排序后一定也出现在中位数的位置上,这样的话就变成了求解一个序列的第K大的数,其中K=length/2;【BFPRT, Time: O(N), Space: log(N))】
2, 记录每个数出现的次数,最好情况遍历前n/2+1个即可,最差O(N), Space: O(N)
3, 要找的数字出现的次数比其他数字出现次数的和还多1,遍历,相同叠加,相异抵消,最后的次数还为1的数字即是。Time: O(N), Space: O(1)
三种方法都不错。还有一种无脑的排序,取中位数即可,排序用快排,时间复杂度N·log(N)
讲一下BFPRT的空间复杂度,select分治过程用到递归,需要栈空间存储,递归次数即是栈空间大小,由于每次只处理划分后的一半,因此最差情况需要递归log(N)次,空间复杂度为log(N)
本题还考察思维的全面性,除了实现功能以外,也应该对无效的输入进行响应的处理,因此有了核实操作。
代码实现:
# 方法1
# # BFPRT解法
# # Time: O(n), Space: O(n)
# class Solution:
# def majorityElement(self, nums) -> int:
# return self.BFPRT(nums, 0, len(nums) - 1, len(nums)//2)
#
# 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
# class Solution:
# # 方法2
# def MoreThanHalfNum_Solution(self, numbers):
# if not numbers:
# return []
# if len(numbers) == 1:
# return numbers[0]
#
# d = {}
# length = len(numbers)
#
# for x in numbers:
# if x not in d:
# d[x] = 1
# else:
# d[x] += 1
# if d[x] == length//2+1:
# return x
# return 0
class Solution:
# 方法3
def MoreThanHalfNum_Solution(self, numbers):
result = numbers[0]
times = 1
for x in numbers:
if x == result:
times += 1
elif times == 0:
result = x
times = 1
else:
times -= 1
# 核实
length = len(numbers)
cnt = 0
for x in numbers:
if x == result:
cnt += 1
if cnt == length // 2 + 1:
return result
return 0
s = Solution()
ans = s.MoreThanHalfNum_Solution([1, 2, 3, 2, 2, 2, 5, 4, 2])
print(ans)
# 方法三
class Gift:
def getValue(self, gifts, n):
if not gifts:
return 0
if n == 1:
return gifts[0]
cnt = 0
for x in gifts:
if cnt == 0:
cur = x
cnt = 1
else:
if x == cur:
cnt += 1
else:
cnt -= 1
# 一定要校验
cnt = 0
for x in gifts:
if x == cur:
cnt += 1
if cnt >= n//2:
return cur
return 0
有序数组中出现次数超过25%的元素和方法三类似。