在该问题中,我们需要在一个数组中找出出现次数超过整个数组中半数的数字。
问题描述
已知在给定的数组中,有一个数字的出现次数超过了数组长度的一半。目标是通过高效的方法找到这个数字。
测试样例
样例 1:
输入:array = [1, 3, 8, 2, 3, 1, 3, 3, 3]
输出:3
样例 2:
输入:array = [5, 5, 5, 1, 2, 5, 5]
输出:5
样例 3:
输入:array = [9, 9, 9, 9, 8, 9, 8, 8]
输出:9
解决思路
根据“大伙数”定理,这个数字的出现次数必然超过数组长度的一半。如果数组长度为 n
,则该数出现的次数大于 ⌊n / 2⌋
(其中 n
可能是奇数)。
由于这个数字出现次数超过半数,因此在不同数字的比较过程中,它最终会被保留下来。这正是 Boyer-Moore 投票算法的核心思想。
Boyer-Moore 投票算法
该算法分为两个阶段:
- 找出可能的大伙数候选值:通过遍历数组并动态更新候选值和计数器,锁定出现次数最多的数字作为候选值。
- 验证候选值是否为大伙数:再次遍历数组,确认候选值是否满足超过半数的条件。
解析代码
from typing import List
def solution(array: List[int]) -> int:
# 第一步:找出候选值
candidate = None
count = 0
for num in array:
if count == 0:
candidate = num
count += 1 if num == candidate else -1
# 第二步:验证候选值是否是大伙数
if array.count(candidate) > len(array) // 2:
return candidate
raise ValueError("No majority element found")
# 测试示例
array1 = [1, 3, 8, 2, 3, 1, 3, 3, 3]
array2 = [5, 5, 5, 1, 2, 5, 5]
array3 = [9, 9, 9, 9, 8, 9, 8, 8]
print(solution(array1)) # 输出:3
print(solution(array2)) # 输出:5
print(solution(array3)) # 输出:9
图解流程
以下图解展示了 Boyer-Moore 投票算法的工作流程。
输入示例:array = [1, 3, 8, 2, 3, 1, 3, 3, 3]
第一步:选出候选值
元素 | candidate | count | 处理结果 |
---|---|---|---|
1 | 1 | 1 | 初始化候选值为 1,count = 1 |
3 | 1 | 0 | 与候选值不同,count 减 1 |
8 | 8 | 1 | count 变为 0,更新候选值为 8 |
2 | 8 | 0 | count 变为 0,更新候选值为 2 |
3 | 3 | 1 | count 变为 0,更新候选值为 3 |
1 | 3 | 0 | 与候选值不同,count 减 1 |
3 | 3 | 1 | 相同,count 加 1 |
3 | 3 | 2 | 相同,count 加 1 |
3 | 3 | 3 | 相同,count 加 1 |
第二步:验证候选值
经过第一步筛选后,候选值为 3
。通过计数验证发现 3
的出现次数为 5,确实超过数组长度的一半,因此 3
是大伙数。
算法复杂度分析
-
时间复杂度:
- 第一次遍历找候选值:
O(n)
。 - 第二次遍历验证候选值:
O(n)
。 - 总体时间复杂度为
O(n)
。
- 第一次遍历找候选值:
-
空间复杂度:
- 只使用了常数个额外变量,空间复杂度为
O(1)
。
- 只使用了常数个额外变量,空间复杂度为
思考
-
算法适用范围:
- 该算法适用于大伙数的特定场景,如果不存在大伙数,需额外的验证机制。
-
改进空间:
- 如果可以预先排序,则可以减少一次验证步骤,通过中位数直接找到大伙数,但时间复杂度增加到
O(n log n)
。
- 如果可以预先排序,则可以减少一次验证步骤,通过中位数直接找到大伙数,但时间复杂度增加到
-
实际应用:
- 适用于调查问卷、投票统计等需要高效筛选出主要结果的场景。