当前位置: 首页 > news >正文

Leetcode刷题记录-Boyer-Moore 投票算法

Boyer-Moore 投票算法

Boyer-Moore 投票算法是一种非常巧妙且高效的算法,专门用于解决“求众数”(Majority Element)及其相关问题。它的核心魅力在于仅需一次遍历常数级别的额外空间就能找到答案。

下面我们来详细拆解这个算法。

1. 算法要解决的问题

标准的“求众数”问题定义如下:

在一个包含 n 个元素的数组中,找到那个出现次数超过 ⌊ n / 2 ⌋ 的元素。题目通常会保证这个众数一定存在。

例如,在 [2, 2, 1, 1, 1, 2, 2] 中,n=7,⌊ n / 2 ⌋ = 3。众数是出现次数超过3次的元素,这里是 2(出现了4次)。

2. 核心思想:阵地攻防与抵消

理解此算法最直观的方式是使用一个“阵地攻防”或“擂主守擂”的类比:

  1. 擂主 (Candidate):我们假设有一个擂主,他占据着阵地。
  2. 生命值 (Count):擂主有一个生命值计数器。

整个算法的遍历过程就像一场战斗:

  • 规则一:如果阵地是空的 (count == 0),那么当前遍历到的新元素 num 就会立刻占领阵地,成为新擂主 (candidate = num),并且拥有 1 点生命值 (count = 1)。
  • 规则二:如果新来的元素 num 和当前擂主 candidate同一个人(同伙),那么擂主的生命值加 1 (count++),表示阵地得到了巩固。
  • 规则三:如果新来的元素 num 和当前擂主 candidate不同的人(敌人),那么敌人会与擂主的一个守卫“同归于尽”,擂主的生命值减 1 (count--)。

最终推论:经过一整轮的战斗(遍历完整个数组),那个最终还能站在阵地上的擂主 (candidate),就是我们要找的众数。

3. 算法步骤

基于上述思想,我们可以总结出清晰的算法步骤:

  1. 初始化两个变量:candidate = Nonecount = 0
  2. 遍历数组 nums 中的每一个元素 num
  3. 在循环中,进行判断:
    a. 如果 count == 0,将 candidate 设为当前的 num,并将 count 设为 1
    b. 否则,如果 num == candidate,则 count1
    c. 否则(num != candidate),则 count1
  4. 遍历结束后,变量 candidate 中存储的值就是众数。

4. 实例演练

我们用数组 nums = [2, 2, 1, 1, 1, 2, 2] 来走一遍流程:

步骤当前元素 numcandidatecount说明
1221count为0,2成为新擂主
2222num == candidate,生命值+1
3121num != candidate,与敌人同归于尽,生命值-1
4120num != candidate,生命值-1,阵地失守
5111count为0,1成为新擂主
6210num != candidate,生命值-1,阵地失守
7221count为0,2成为新擂主

遍历结束,最终的 candidate2,这就是众数。

5. 算法正确性证明:为什么它一定有效?

因为众数的数量超过了数组长度的一半,这意味着众数的数量比所有其他元素数量的总和还要多

  • 在“同归于尽”的过程中,每一次 count-- 操作都意味着一个众数和一个非众数进行了抵消。
  • 由于众数的“兵力”比所有其他“敌人”的兵力总和还要强大,即使所有敌人都上来一对一兑子,最后也必然会有众数的“士兵”存活下来。
  • 因此,在经历所有的抵消之后,最终能够让 count 大于 0 并留在 candidate 位置的,必然是那个众数。

6. 扩展应用:寻找超过 n/3 的元素

这个算法的思想还可以扩展。例如,寻找数组中所有出现次数超过 ⌊ n / 3 ⌋ 的元素(LeetCode 229. 求众数 II)。

此时,满足条件的元素最多只会有两个。我们可以用同样“抵消”的思想:

  • 维护两个 candidate两个 count
  • 当来了一个新元素 num
    • 如果 numcandidate1candidate2 相同,则对应的 count 加 1。
    • 如果两个 candidate 的位置都没满,则 num 成为其中一个 candidate
    • 如果 num 与两个 candidate 都不同,则两个 count 同时减 1。这就相当于三个不同的元素凑在一起“同归于尽”了。

最后还需要再遍历一次数组,验证留下的两个 candidate 是否真的满足次数超过 n/3 的要求,因为此时不能保证留下的就一定是答案。

总结

特性描述
时间复杂度O(n)O(n)O(n),因为只需要对数组进行一次完整的遍历。
空间复杂度O(1)O(1)O(1),因为只使用了 candidatecount 两个额外的变量,与输入规模无关。
核心思想抵消/投票。利用众数数量上的绝对优势,保证其在两两抵消后仍能留存。
适用场景寻找数组中出现次数超过 1/k 的元素,尤其是在要求线性和常数空间时。

Boyer-Moore 投票算法是算法面试中的常客,因为它完美地展现了如何用巧妙的思路来优化时间和空间复杂度。

169.majority element

题目描述:
给定一个大小为 n 的整数数组 nums,请返回其中的多数元素(majority element)

多数元素指的是在数组中出现次数超过 ⌊n / 2⌋ 次的那个元素。
可以假设数组中一定存在多数元素。


示例 1:
输入:nums = [3,2,3]
输出:3

示例 2:
输入:nums = [2,2,1,1,1,2,2]
输出:2


约束条件:

  • n == nums.length
  • 1 <= n <= 5 * 10⁴
  • -10⁹ <= nums[i] <= 10⁹

进阶要求(Follow-up):
你能否用线性时间 O(n) 并且只使用 常数级额外空间 O(1) 来解决这个问题?


这道题是著名的 “多数投票算法(Boyer–Moore Voting Algorithm)” 经典题:
算法思想是维护一个候选元素 candidate 和一个计数器 count
每当遇到相同元素就加一,不同元素就减一;当计数为零时换候选。
因为多数元素的出现次数超过一半,所以最后剩下的候选就是答案。

代码实现

class Solution(object):def majorityElement(self, nums):cnt=0candidate=Nonefor num in nums:if cnt == 0:candidate = numif num == candidate:cnt+=1else:cnt-=1return candidate

其他方法

方法一:哈希表计数法 (最直观)

这是最容易想到的方法。我们可以遍历一遍数组,用一个哈希表(在 Python 中是字典)来记录每个数字出现的次数。然后再遍历一遍哈希表,找出那个出现次数超过 ⌊n / 2⌋ 的数字。

思路

  1. 创建一个哈希表 counts 用来存储 {数字: 出现次数}
  2. 遍历 nums 数组,每遇到一个数,就在哈希表中给它的计数值加 1。
  3. 遍历完后,再检查哈希表中的每个键值对,返回那个计数值大于 len(nums) / 2 的数字。

Python 代码示例

def majorityElement_hashmap(nums):counts = {}n = len(nums)for num in nums:counts[num] = counts.get(num, 0) + 1for num, count in counts.items():if count > n / 2:return num
  • 时间复杂度: O(n)O(n)O(n),因为我们遍历数组一次,遍历哈希表一次。
  • 空间复杂度: O(n)O(n)O(n),在最坏的情况下(例如,数组中一半是众数,另一半是各不相同的数),哈希表需要存储大约 n/2 个元素。
  • counts[num] = counts.get(num, 0) + 1
  1. counts.get(num, 0)

    • .get() 是字典(哈希表)的一个方法,用来安全地获取一个键(key)对应的值(value)。

    • 它接收两个参数:key 和一个可选的 default_value(默认值)。

    • 情况A:如果 num 已经是 counts 字典中的一个键,那么 counts.get(num, 0) 就会返回 num 对应的当前计数值。例如,如果 counts{'apple': 3},那么 counts.get('apple', 0) 就会返回 3

    • 情况B:如果 num 还不是 counts 字典中的键(也就是我们第一次遇到这个数字),直接用 counts[num] 会报错(KeyError)。但 .get() 方法的美妙之处就在于,它不会报错,而是会返回你提供的默认值,这里是 0
      ... + 1

    • 这一步很简单,就是把上一步获取到的值(无论是旧的计数值还是默认值 0)加 1。

    • 情况A:如果 num 已经存在,我们就得到了 旧的计数值 + 1

    • 情况B:如果 num 是第一次出现,我们就得到了 0 + 1,也就是 1

  2. counts[num] = ...

    • 这是标准的字典赋值操作。它会把右边计算出的新计数值,更新(或创建)到 counts 字典中,键为 num

整个流程串起来就是:

“查看字典 counts 中有没有 num 这个键。如果有,就取出它现在的值;如果没有,就把它看作是 0。然后,将这个值加 1,再存回字典里,键仍然是 num。”

对比一下常规写法:
如果不使用 .get() 方法:

if num in counts:# 如果 num 已经存在,就在原来的基础上加 1counts[num] = counts[num] + 1
else:# 如果 num 是第一次出现,就创建这个键,并把值设为 1counts[num] = 1
方法二:排序法 (很巧妙)

这个方法利用了“众数”的定义。一个数如果出现的次数超过了数组长度的一半,那么当我们把数组排好序之后,这个数必然会出现在数组最中间的位置

思路
想象一下,如果一个队伍的人数超过了总人数的一半,无论他们怎么站队,队伍中间的那个人必然是这个队伍里的人。

例如 [2, 2, 1, 1, 1, 2, 2],排序后是 [1, 1, 1, 2, 2, 2, 2]。数组长度是 7,中间位置的索引是 7 // 2 = 3nums[3] 的值是 2,就是众数。

Python 代码示例

def majorityElement_sort(nums):nums.sort()return nums[len(nums) // 2]
  • 时间复杂度: O(nlog⁡n)O(n \log n)O(nlogn),主要开销是排序。
  • 空间复杂度: O(1)O(1)O(1)O(log⁡n)O(\log n)O(logn),取决于编程语言内置排序算法的实现。
http://www.dtcms.com/a/458426.html

相关文章:

  • 千图素材网站wordpress 侧边栏代码
  • 建设网站询价对比表模板什么网站时候做伪静态
  • 一个开源免费的TTS工具2.0
  • 引流网站建设教程做网站设计的长宽一般是多少
  • 网站 解析iis怎么搭建设计网站
  • 网站建设公司招聘校园微网站建设方案ppt模板
  • 徐州营销型网站制使开发一个网站成本
  • 【framebuffer】
  • 【PAG】一个PAGView和多个PAGImageView分别渲染多个pag文件
  • 专门做婚庆的网站有哪些广州市品牌网站建设企业
  • 上海市网站seo公司网站活动怎么做
  • 昆山网站制作公司网站建设征求意见通知
  • 网站备案 如何填网站开发三层
  • 西樵网站设计网页设计与制作 pdf
  • 海淀教育互动平台网站建设本溪市城乡住房建设厅网站
  • xxx网站建设策划书范文精通网站建设电子档
  • v-if和v-for在同一个元素上的使用
  • 电商平台介绍网站模板济南 网站推广
  • 百度网站两两学一做心得体会郑州 网站建设公司
  • 网站怎么做充值系统下载深圳设计公司深圳设计公司排名
  • 如何解决 pip install -r requirements.txt 无效可编辑项 ‘e .‘(-e 拼写错误)问题
  • 番禺网站制作技术wordpress模板不显示
  • 门户网站建设自查整改报告泊头网站建设价格
  • CMake进阶:解析自定义函数 / 宏的可变参数(ARGN)的指令cmake_parse_arguments
  • 中山市 有限公司网站建设页面设计制作网站
  • 洛宁县东宋乡城乡建设局网站怎样维护公司网站
  • 最新Kolmogorov-Arnold网络架构下的KANConv
  • 【C语言操作符终极指南】万字总结:从二进制到表达式求值,全方位解析+避坑指南
  • 大模型-扩散模型(Diffusion Model)原理讲解(5)
  • 基于51单片机的多功能电子万年历