# Boyer-Moore 投票算法在此题目中的应用

103 阅读5分钟

这道题的描述是下面这样:

8 找出整型数组中占比超过一半的数 问题描述 小R从班级中抽取了一些同学,每位同学都会给出一个数字。已知在这些数字中,某个数字的出现次数超过了数字总数的一半。现在需要你帮助小R找到这个数字。 

测试样例 :

样例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

题目分析

这是一道典型的“多数元素”问题,要求在一个整型数组中找到出现次数超过数组长度一半的数字。题目明确保证这样的数字一定存在。

方法 1:排序法

思路:

将数组排序后,数组中间的元素一定是出现次数最多的元素(多数元素),因为它的次数超过了数组的一半。

实现代码:

def solution(array):
    array.sort()  # 排序
    return array[len(array) // 2]  # 返回中间的元素


这是一种比较简单的思路,排序后,多数元素必定出现在数组的中间位置,因为它的次数超过了数组长度的一半的时间复杂度:O(n log n),由于排序占主导。空间复杂度:O(1),排序是在原地进行的。也是一开始最快想到的方法。但是如果是这样做完就结束了,这道题可以说是比较简单的。

然后就进行了进一步的思考。

方法2:使用哈希表统计每个数字的出现次数

我们可以利用一个哈希表(字典)来统计每个数字的出现次数,然后判断哪个数字的出现次数超过数组长度的一半。

实现代码:

def solution(array):    # 用字典统计每个数字出现的次数    count_dict = {}    for num in array:        if num in count_dict:            count_dict[num] += 1        else:            count_dict[num] = 1        # 如果当前数字的次数超过数组长度的一半,直接返回        if count_dict[num] > len(array) // 2:            return numif __name__ == "__main__":    # Add your test cases here    print(solution([1, 3, 8, 2, 3, 1, 3, 3, 3]) == 3)

解法说明:

  1. 初始化一个空字典 count_dict,用于存储每个数字及其出现次数。
  2. 遍历数组:如果数字在字典中已存在,则次数加 1。如果数字不在字典中,则将其加入字典,次数初始化为 1。
  3. 在每次更新计数后,判断当前数字的出现次数是否超过数组长度的一半。如果是,则直接返回该数字。

时间复杂度与空间复杂度:时间复杂度:O(n),需要遍历数组一次。空间复杂度:O(n),因为需要额外的字典来存储数字和出现次数。

接着,我们看到一个更加特别的算法Boyer-Moore 投票算法

我们首先看到这道题目的开头:

【已知在这些数字中,某个数字的出现次数超过了数字总数的一半】这个算法体现的是减而治之的算法思想,本质上是从问题的规模上不断的缩小众数的求解范围。

具体操作如下在于其中的核心思想

  1. 候选元素

    • 选出一个可能的“多数元素”,称为候选元素。
    • 在数组中,每当遇到一个与候选元素相同的元素,就为候选元素“加票”;否则,为候选元素“减票”。
  2. 计数机制

    • 如果当前计数为 0,就更新候选元素为当前的元素,并重置计数为 1。
  3. 验证

  • 因为只有一个元素出现次数超过一半,最后剩下的候选元素就是多数元素。

具体操作如下:

  • 初始化一个 candidate 变量(候选元素)为 None,计数器 count 为 0。

  • 遍历数组:

    • 如果 count == 0,将当前元素作为新的候选元素。
    • 如果当前元素与候选元素相同,则 count += 1
    • 如果不同,则 count -= 1
  • 最后,candidate 是多数元素。

  • 代码实现

    def solution(array):    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    return None  # 理论上不会到达这里,因为题目保证存在多数元素
    

Boyer-Moore 投票算法的优势在于它以一种极为高效的方式解决了寻找多数元素的问题。首先,它的时间复杂度为 O(n),这是该问题的最优复杂度之一,因为需要遍历整个数组来确定候选元素。而传统方法,例如使用哈希表统计出现次数或排序后寻找,都需要额外的时间或空间资源,而 Boyer-Moore 算法通过“计数”和“更新候选”的策略,显著减少了运算步骤,确保了线性时间内完成任务。这种算法的空间复杂度为 O(1),因为它只需要维护两个变量:一个用于存储当前候选元素,一个用于计数。与需要额外存储的哈希表或分治递归相比,这种方法在空间上的要求几乎可以忽略。对于内存有限的场景或者大规模数据处理,Boyer-Moore 算法的这一特性尤其突出。

总的来说,Boyer-Moore 投票算法的逻辑非常简洁优雅,易于实现和理解。它巧妙地通过投票机制不断筛选出潜在的多数元素,最终利用数学规律确保找到正确的答案。这种策略尤其适合处理连续流数据,因为它只需在单次遍历中动态更新候选元素,而不需要对数据整体存储或回溯分析。

方法

时间复杂度

空间复杂度

备注

哈希表统计法

O(n)

O(n)

占用额外空间

排序法

O(n log n)

O(1)(原地排序)

效率不及 O(n) 方法

Boyer-Moore 投票

O(n)

O(1)

效率高且无需额外空间