超过半数的数字 | 豆包MarsCode AI刷题

106 阅读5分钟

超过半数的数字 | 豆包MarsCode AI刷题

找出整型数组中占比超过一半的数 - MarsCode

本题使用了Boyer-Moore投票算法,很高效率的一个寻找多数元素的算法。但是要注意使用时必须注意目标数组必须包含多数元素,因为其并不严格返回多数元素。

摘要

本文介绍了如何在一个数组中找到出现次数超过总数一半的数字。通过Boyer-Moore投票算法,我们可以在 O(n)O(n) 时间内找到这个“多数元素”。该算法逻辑简单,且空间复杂度为 O(1)O(1),非常适合这种寻找多数元素的问题。文中包含Python和Go的实现代码。

问题描述

在一个包含多个数字的数组中,某个数字的出现次数超过了数组长度的一半。目标是找到这个“多数元素”。

示例

  • 输入:array = [1, 3, 8, 2, 3, 1, 3, 3, 3]
    输出:3

  • 输入:array = [5, 5, 5, 1, 2, 5, 5]
    输出:5

  • 输入:array = [9, 9, 9, 8, 9, 8, 8, 8, 8]
    输出:9

原理分析

1. 投票算法的基本思想

投票算法的核心思想是:

  • 如果我们把“多数元素”看作一个候选人,其余元素看作反对者,最终多数元素会“赢得选举”。
  • 具体来说,假设在数组中,每个元素都可以投票支持一个候选人。
    • 如果是多数元素,它会得到更多的投票,因为它的出现次数超过了一半。
    • 如果不是多数元素,那么它会被其他不同的元素抵消。

2. 算法步骤

Boyer-Moore 投票算法的流程如下:

  1. 初始化:设置一个候选元素 candidate 和计数器 count,初始值分别为 00
  2. 遍历数组:对于数组中的每个元素,按照以下规则更新 candidatecount
    • 如果 count0,说明当前没有候选元素,或者当前的候选元素已经被抵消完了。在这种情况下,将当前的元素设置为新的候选元素,并将 count 设为 1
    • 如果当前元素等于 candidate,说明它支持当前的候选元素,因此增加 count
    • 如果当前元素不等于 candidate,说明它不支持当前的候选元素,因此减少 count
  3. 最终结果:在遍历完数组后,candidate 就是数组中的多数元素。

3. 算法的正确性证明

  • 由于题目保证多数元素的出现次数超过一半,所以当 count 被抵消到 0 时,即使当前候选元素被更换成了新的元素,多数元素依然会“支持”新的候选元素,最终导致 candidate 会稳定在多数元素上。
  • 当遍历完成时,candidate 一定是出现次数超过一半的元素。

4. 时间复杂度和空间复杂度

  • 时间复杂度:O(n),只需要遍历数组一遍。
  • 空间复杂度:O(1),只需要常数级别的额外空间。

5. 示例演示

示例 1

假设数组为 [1, 3, 8, 2, 3, 1, 3, 3, 3]

我们来一步步应用 Boyer-Moore 投票算法:

步骤当前元素候选元素 (candidate)计数器 (count)说明
初始00初始状态,候选元素和计数器均为 0
1111count 为 0,将 1 设为候选元素,count 为 1
23103 不等于候选元素 1count 减 1 为 0
3881count 为 0,将 8 设为候选元素,count 为 1
42802 不等于候选元素 8count 减 1 为 0
5331count 为 0,将 3 设为候选元素,count 为 1
61301 不等于候选元素 3count 减 1 为 0
73313 等于候选元素 3count 增 1 为 1
83323 等于候选元素 3count 增 1 为 2
93333 等于候选元素 3count 增 1 为 3

最终,candidate3,即多数元素是 3

为什么 Boyer-Moore 投票算法有效?

  • 每次 count 减少到 0 时,表示当前的候选元素已经“被击败”,需要重新选择一个新的候选元素。
  • 由于多数元素的数量超过一半,最终它会占据主导地位,即使遇到不同的元素会暂时减少它的 count,但在算法的最后阶段,多数元素仍然会留下来作为最终的候选。

Boyer-Moore 投票算法的应用条件

  • 已知数组中一定存在多数元素:Boyer-Moore 投票算法适用于这种情况。如果数组中没有多数元素,算法最后得到的 candidate 并不一定满足题意。
  • 要求在 O(n) 时间内解决:Boyer-Moore 投票算法非常高效,只需要遍历数组一次。

代码实现

Python代码

from typing import List

def solution(array: List[int]) -> int:
    """
    该函数接收一个整数数组 array,并返回其中的“候选元素”。
    使用的是“Boyer-Moore 投票算法”来找到数组中出现次数超过一半的元素(如果存在)。
    """
    count = 0
    candidate = array[0]

    for num in array:
        if count == 0:
            # 如果计数为 0,则更新候选元素为当前元素
            candidate = num
            count = 1
        elif candidate == num:
            # 如果当前元素等于候选元素,增加计数
            count += 1
        else:
            # 如果当前元素不等于候选元素,减少计数
            count -= 1

    return candidate


if __name__ == "__main__":
    # 测试用例
    print(solution([1, 3, 8, 2, 3, 1, 3, 3, 3]) == 3)  # 应输出 True
    print(solution([5, 5, 5, 1, 2, 5, 5]) == 5)       # 应输出 True
    print(solution([9, 9, 9, 8, 9, 8, 8, 8, 8]) == 9) # 应输出 True

Go语言代码

package main

import "fmt"

func solution(array []int) int {
    count := 0
    candidate := array[0]
    
    for _, num := range array {
        if count == 0 {
            // 重新设定候选元素
            candidate = num
            count = 1
        } else if candidate == num {
            // 增加计数
            count++
        } else {
            // 减少计数
            count--
        }
    }
    return candidate
}

func main() {
    // 测试用例
    fmt.Println(solution([]int{1, 3, 8, 2, 3, 1, 3, 3, 3}) == 3)  // 应输出 true
    fmt.Println(solution([]int{5, 5, 5, 1, 2, 5, 5}) == 5)        // 应输出 true
    fmt.Println(solution([]int{9, 9, 9, 8, 9, 8, 8, 8, 8}) == 9)  // 应输出 true
}