8.找出整型数组中占比超过一半的数 | 豆包MarsCode AI刷题

1 阅读3分钟

在该问题中,我们需要在一个数组中找出出现次数超过整个数组中半数的数字。

问题描述

已知在给定的数组中,有一个数字的出现次数超过了数组长度的一半。目标是通过高效的方法找到这个数字。

测试样例

样例 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 投票算法

该算法分为两个阶段:

  1. 找出可能的大伙数候选值:通过遍历数组并动态更新候选值和计数器,锁定出现次数最多的数字作为候选值。
  2. 验证候选值是否为大伙数:再次遍历数组,确认候选值是否满足超过半数的条件。

解析代码

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]

第一步:选出候选值

元素candidatecount处理结果
111初始化候选值为 1,count = 1
310与候选值不同,count 减 1
881count 变为 0,更新候选值为 8
280count 变为 0,更新候选值为 2
331count 变为 0,更新候选值为 3
130与候选值不同,count 减 1
331相同,count 加 1
332相同,count 加 1
333相同,count 加 1

第二步:验证候选值

经过第一步筛选后,候选值为 3。通过计数验证发现 3 的出现次数为 5,确实超过数组长度的一半,因此 3 是大伙数。


算法复杂度分析

  • 时间复杂度

    • 第一次遍历找候选值:O(n)
    • 第二次遍历验证候选值:O(n)
    • 总体时间复杂度为 O(n)
  • 空间复杂度

    • 只使用了常数个额外变量,空间复杂度为 O(1)

思考

  1. 算法适用范围

    • 该算法适用于大伙数的特定场景,如果不存在大伙数,需额外的验证机制。
  2. 改进空间

    • 如果可以预先排序,则可以减少一次验证步骤,通过中位数直接找到大伙数,但时间复杂度增加到 O(n log n)
  3. 实际应用

    • 适用于调查问卷、投票统计等需要高效筛选出主要结果的场景。