【每日算法Day 90】5种方法:求解数组中出现次数超过一半的那个数

简介: 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。你可以假设数组是非空的,并且给定的数组总是存在多数元素。

题目链接


LeetCode 面试题39. 数组中出现次数超过一半的数字[1]

题目描述


数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

说明:

  • 1 <= 数组长度 <= 50000

示例1

输入:[1, 2, 3, 2, 2, 2, 5, 4, 2]输出:2

题解


哈希表


这个方法最简单,用哈希表记录每个数字出现的次数,最后看哪个数字次数超过一半就行了。

image.png

排序


对数组从小到大进行排序,那么众数一定在 nums[n/2] 处。为什么呢?

因为排序后相同的数都连续了,所以众数最左端的极限情况就是从下标 0 开始往后排,那么因为超过了一半,所以尾部下标一定会超过 n/2 。而最右端的极限情况就是从下标 n-1 往前排,因为超过了一半,所以头部下标也会在 n/2 之前。

综上,众数所在的区间一定会包含下标 n/2

image.png

随机采样


其实我第一个想到的方法反而是这个反常规的随机采样方法。因为众数超过了一半,所以采样大概率会采到这个众数。

那么我们随机采样一个数,然后遍历一遍数组看它的个数。如果个数超过了一半就是它了,否则继续采样,直到采到众数。

image.png

分治


如果把区间 [0, n-1] 平均分成两半,那么我们可以证明,原来的众数在某一半区间里依然是众数。

为什么呢?反证法,假设两半区间的众数都不是原来的众数,那么在左半区间原来的众数一定小于一半,右半区间也是的。加起来之后总数一定小于一半的,和条件是矛盾的。

所以我们递归求解两半区间的众数,然后看哪个数出现次数较多,众数就是它了。

image.png

摩尔投票


这个方法我一开始也想到了,但是没有想到这竟然有理论解释,而且是大名鼎鼎的摩尔投票算法。

它的主要步骤是这样的:

  • 初始化两个变量, cand 表示候选人,cnt 表示赞同它的票数。
  • 如果 cnt = 0,那么 cand 就设置为当前的数字。
  • 如果 cand 等于当前数字,那么票数 cnt 加一,否则票数减一。
  • 最后 cand 就是得票超过一半的众数。

严格证明比较复杂,是一篇论文,这里说个比较好理解的思路:

  • 如果当前候选人是众数,那么其他的众数会支持自己,其他的数反对自己。但是因为众数超过了一半,所以众数最后一定会当选。
  • 如果当前候选人不是众数,那么就惨了,其他的数和众数全都会反对他。那反对票远远超过一半了,肯定会下台,然后换候选人。
  • 上面两种情况会在 cnt = 0 的时刻进行转换,也就是换候选人。

image.png

代码


哈希表(c++)

classSolution {
public:  
intmajorityElement(vector<int>&nums) {   
intn=nums.size();     
unordered_map<int, int>mp;     
for (autox : nums) mp[x]++;   
for (auto [k, v] : mp) {     
if (v>n/2) returnk;    
        }       
return-1; 
    }
};

排序(c++)

classSolution {
public:  
intmajorityElement(vector<int>&nums) {
intn=nums.size();  
sort(nums.begin(), nums.end());    
returnnums[n/2];  
    }
};

随机采样(c++)

classSolution {
public:  
intmajorityElement(vector<int>&nums) { 
intn=nums.size();    
while (1) {      
intidx=rand() %n; 
intcnt=0;      
for (autox : nums) {     
if (x==nums[idx]) cnt++; 
            }       
if (cnt>n/2) returnnums[idx];    
        }      
return-1; 
    }
};

分治(c++)

classSolution {
public:  
intnumCount(vector<int>&nums, intx, intl, intr) {   
intcnt=0;        for (inti=l; i<=r; ++i) {   
if (nums[i] ==x) cnt++;   
                                                         }     
returncnt; 
    }
intfindMajority(vector<int>&nums, intl, intr) {    
if (l==r) returnnums[l];   
intm=l+(r-l)/2;    
intml=findMajority(nums, l, m);
intmr=findMajority(nums, m+1, r); 
if (ml==mr) returnml;    
intcl=numCount(nums, ml, l, r);     
intcr=numCount(nums, mr, l, r);    
returncl<cr?mr : ml;  
    }
intmajorityElement(vector<int>&nums) {   
intn=nums.size();   
returnfindMajority(nums, 0, n-1); 
    }
};

摩尔投票(c++)

classSolution {
public:  
intmajorityElement(vector<int>&nums) { 
intcand=0, cnt=0;   
for (autox : nums) {    
if (!cnt) cand=x;     
if (x==cand) cnt++;    
elsecnt--;   
        }       
returncand; 
    }
};

哈希表(python)

classSolution:
defmajorityElement(self, nums): 
counts=collections.Counter(nums)
returnmax(counts.keys(), key=counts.get)

排序(python)

classSolution:    defmajorityElement(self, nums):        nums.sort()        returnnums[len(nums)//2]

随机采样(python)

importrandomclassSolution: 
defmajorityElement(self, nums):   
majority_count=len(nums)//2whileTrue:      
candidate=random.choice(nums)   
ifsum(1foreleminnumsifelem==candidate) >majority_count:    
returncandidate

分治(python)

classSolution:   
defmajorityElement(self, nums, lo=0, hi=None): 
defmajority_element_rec(lo, hi):  
iflo==hi: 
returnnums[lo]    
mid= (hi-lo)//2+loleft=majority_element_rec(lo, mid) 
right=majority_element_rec(mid+1, hi)  
ifleft==right:     
returnleftleft_count=sum(1foriinrange(lo, hi+1) ifnums[i] 
==left)    
right_count=sum(1foriinrange(lo, hi+1) ifnums[i]
==right)   
returnleftifleft_count>right_countelserightreturnmajority_element_rec(0, len(nums)-1)

摩尔投票(python)

classSolution: 
defmajorityElement(self, nums): 
count=0candidate=Nonefornuminnums:    
ifcount==0:    
candidate=numcount+= (1ifnum==candidateelse-1) 
returncandidate

关注【算法码上来】,每日算法干货马上就来!

参考资料


[1]

LeetCode 面试题39. 数组中出现次数超过一半的数字: https://leetcode-cn.com/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/

image.png

作者简介:godweiyang知乎同名华东师范大学计算机系硕士在读,方向自然语言处理与深度学习喜欢与人分享技术与知识,期待与你的进一步交流~


相关文章
|
3天前
|
算法 数据安全/隐私保护 计算机视觉
基于二维CS-SCHT变换和LABS方法的水印嵌入和提取算法matlab仿真
该内容包括一个算法的运行展示和详细步骤,使用了MATLAB2022a。算法涉及水印嵌入和提取,利用LAB色彩空间可能用于隐藏水印。水印通过二维CS-SCHT变换、低频系数处理和特定解码策略来提取。代码段展示了水印置乱、图像处理(如噪声、旋转、剪切等攻击)以及水印的逆置乱和提取过程。最后,计算并保存了比特率,用于评估水印的稳健性。
|
3天前
|
存储 算法 Java
数据结构与算法 数组和链表
数据结构与算法 数组和链表
12 0
|
3天前
|
算法
【免费】基于SOE算法的多时段随机配电网重构方法
【免费】基于SOE算法的多时段随机配电网重构方法
|
3天前
|
存储 算法
Leetcode 30天高效刷数据结构和算法 Day1 两数之和 —— 无序数组
给定一个无序整数数组和目标值,找出数组中和为目标值的两个数的下标。要求不重复且可按任意顺序返回。示例:输入nums = [2,7,11,15], target = 9,输出[0,1]。暴力解法时间复杂度O(n²),优化解法利用哈希表实现,时间复杂度O(n)。
22 0
|
3天前
|
机器学习/深度学习 自然语言处理 算法
深度解析深度学习中的优化算法:从梯度下降到自适应方法
【4月更文挑战第28天】 在深度学习模型训练的复杂数学迷宫中,优化算法是寻找最优权重配置的关键导航者。本文将深入探讨几种主流的优化策略,揭示它们如何引导模型收敛至损失函数的最小值。我们将比较经典的批量梯度下降(BGD)、随机梯度下降(SGD)以及动量概念的引入,进一步探索AdaGrad、RMSProp和Adam等自适应学习率方法的原理与实际应用。通过剖析这些算法的理论基础和性能表现,我们旨在为读者提供一个关于选择合适优化器的参考视角。
|
3天前
|
编解码 算法 数据可视化
【视频】时间序列分类方法:动态时间规整算法DTW和R语言实现
【视频】时间序列分类方法:动态时间规整算法DTW和R语言实现
|
3天前
|
机器学习/深度学习 存储 人工智能
一阶优化算法启发,北大林宙辰团队提出具有万有逼近性质的神经网络架构的设计方法
【4月更文挑战第19天】北京大学林宙辰团队在深度学习领域取得突破,提出基于一阶优化算法的神经网络设计方法,构建具有万有逼近性质的模型,提升训练速度和泛化能力。该方法利用一阶导数信息,高效处理大规模问题。虽然面临非光滑优化和收敛速度挑战,但团队通过正则化和自适应学习率等策略进行改进,相关研究在多个标准数据集上表现出色。
16 1
|
3天前
|
机器学习/深度学习 算法
R语言非参数方法:使用核回归平滑估计和K-NN(K近邻算法)分类预测心脏病数据
R语言非参数方法:使用核回归平滑估计和K-NN(K近邻算法)分类预测心脏病数据
|
3天前
|
算法 JavaScript 前端开发
贪心算法】按要求补齐数组
贪心算法】按要求补齐数组
10 0