题目
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
例如输入一个长度为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)
本题还考察思维的全面性,除了实现功能以外,也应该对无效的输入进行响应的处理,因此有了核实操作。
代码实现:
# class Solution:
# # 方法1
# def get_median(self, nums):
# '''计算5个数的中位数'''
# begin, end = 0, len(nums) - 1
#
# sum = begin + end
# mid = sum // 2 + sum % 2 # 这个地方加上sum%2是为了确保偶数个数时我们求的是中间两个数的后一个
# return sorted(nums)[mid] # 常量级别,影响不大,也可改用插入排序
#
# def BFPRT(self, nums, left, right):
# '''选择partition基准: 划分每5个一组,求每组的中位数'''
# num = right-left + 1
# offset = 0 if num % 5 == 0 else 1 # 最后如果剩余的数不足5个,我们也将其分成一个小组,和前面同等对待
# group = num//5 + offset
#
# median = [] # 中位数数组
# for i in range(group):
# begin = left + i * 5
# end = begin + 4
# Median = self.get_median(nums[begin:min(end, right) + 1])
# median.append(Median)
# return self.get_median(median) # 这里是求中位数数组的中位数,再调用getmedian不就行了,为啥有的人还要用下面递归select,虽然也是正确的,但直接getmedian快一点啊
# # return select(median, 0, groups-1, groups//2) # 求出生成好的median数组的中位数,作为partation函数的划分值
#
# def partition(self, nums, left, right, base):
# '''将基准base归位'''
# 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[max_index], nums[i] = nums[i], nums[max_index]
# max_index += 1
# nums[max_index], nums[right] = nums[right], nums[max_index]
# return max_index
#
# def select(self, nums, left, right, K):
# '''快速选择过程'''
# if left == right:
# return nums[left]
#
# base = self.BFPRT(nums, left, right) # 选择基准
# base_index = self.partition(nums, left, right, nums.index(base)) # 基准归位
#
# if base_index == K: # 判断目前已归位的基准,是不是第K位
# return nums[base_index]
# elif base_index > K: # 递归左半部分
# return self.select(nums, left, base_index - 1, K)
# else: # 递归右半部分
# return self.select(nums, base_index + 1, right, K)
#
# def MoreThanHalfNum_Solution(self, numbers):
# if not numbers:
# return 0
#
# left, right = 0, len(numbers) - 1
# length = len(numbers)
# K = right // 2
# ans = self.select(numbers, left, right, right - K + 1) # 第K大,就是第N-K+1小
#
# # 核实
# cnt = 0
# for x in numbers:
# if x == ans:
# cnt += 1
# if cnt == length // 2 + 1:
# return ans
# return 0
# 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)
有序数组中出现次数超过25%的元素和方法三类似。