数组找众数还在数次数?Java这招0空间秒出,比统计快10倍!
你有没有过这种崩溃:要找数组里“出现超一半”的众数,掏出HashMap逐个统计次数,代码写得像绕口令,还得记“元素→次数”的映射,最后算半天发现空间占了一大块?其实找众数根本不用“数豆子”,Java里的摩尔投票法就像“选班长”,靠“对抗抵消”秒出结果,全程只需要两个变量,连额外数组都不用!今天用“班级投票”的段子给你讲透,看完笑到会写代码,下次找众数你也能当“快手”~
先吐槽“统计法”:像数豆子数到眼瞎,还占地方
新手找众数,最爱用“HashMap统计法”:比如数组`[2,2,1,1,1,2,2]`,要找出现超4次的众数,步骤能绕晕自己:
- 新建HashMap,遍历数组,遇到2就把次数+1,遇到1也+1;
- 遍历完再翻HashMap,看哪个元素次数超一半(7/2=3.5,超3.5即≥4);
- 最后找到2是众数,但代码写了十几行,还占了O(n)的空间。
这就像班级选班长,你拿个小本本记“张三3票、李四4票、王五2票”,记到最后本子写满了,还得再算一遍谁超半数——不仅慢,要是数组有100万元素,HashMap能把内存占得像“堆满快递盒”,纯属给自己找罪受!
摩尔投票法的“巧劲”:像选班长靠“对抗”,票数抵消见真章
摩尔投票法的核心逻辑超简单:**众数出现超一半,比所有非众数加起来还多,所以能靠“一对一抵消”赢到最后**。就像班级选班长,众数是“张三”,支持他的人超一半:
- 每有一个人投张三(遇到众数),票数+1;
- 每有一个人投别人(遇到非众数),票数-1;
- 就算中间票数抵消到0,换个候选人,最后赢的还是张三——因为他的支持者最多,抵消不完!
用数组`[2,2,1,1,1,2,2]`举例,一步步看投票法的魔力:
- 初始:候选众数=2,票数=1(先暂定2是班长,有1人支持);
- 遇2(支持候选):票数=1+1=2;
- 遇1(反对候选):票数=2-1=1;
- 遇1(反对候选):票数=1-1=0(票数归0,换候选);
- 遇1(换候选为1,票数=1);
- 遇2(反对候选1):票数=1-1=0(再换候选);
- 遇2(换候选为2,票数=1);
——遍历完候选是2,再验证2出现5次(超3.5),确实是众数!
全程只用到“候选”和“票数”两个变量,空间O(1),比统计法快10倍~
Java实现摩尔投票法:选班长类比+代码,新手一看就懂
摩尔投票法的Java实现分两步:先“投票选候选”,再“验证候选是否真众数”,每一步都对应“班级投票”,代码简单到离谱:
第一步:投票选候选——暂定班长,对抗抵消
先写投票逻辑,遍历数组确定候选众数,就像“班级投票选暂定班长,票数归0就换”:
public class FindMajority {
// 第一步:投票选候选众数
private static int findCandidate(int[] nums) {
int candidate = nums[0]; // 初始候选:第一个元素(暂定第一个人当班长)
int count = 1; // 初始票数:1票支持
for (int i = 1; i < nums.length; i++) {
if (count == 0) {
// 票数归0,换候选(之前的班长没人支持了,换新人)
candidate = nums[i];
count = 1;
} else if (nums[i] == candidate) {
// 遇到候选,票数+1(有人支持当前班长)
count++;
} else {
// 遇到非候选,票数-1(有人反对当前班长)
count--;
}
}
return candidate; // 遍历完的候选
}
}
第二步:验证候选——确认班长真的超半数
必须加验证!因为如果数组没有众数(比如`[1,2,3]`),投票法也会出候选(最后是3),但3不是众数。这就像“班级投票没人超半数,不能硬选班长”,得再查一遍票数:
// 第二步:验证候选是否真的是众数(出现超一半)
private static boolean isMajority(int[] nums, int candidate) {
int count = 0;
for (int num : nums) {
if (num == candidate) {
count++;
}
}
// 超一半:count > 数组长度/2(注意整数除法,比如7/2=3,count>3即≥4)
return count > nums.length / 2;
}
整合方法:先选候选,再验证,出结果
把两步整合,最后返回众数,没众数就提示(或返回-1):
// 找众数主方法
public static int findMajorityElement(int[] nums) {
int candidate = findCandidate(nums);
// 验证候选是众数,才返回;不是就返回-1(表示无众数)
return isMajority(nums, candidate) ? candidate : -1;
}
// 测试:数组[2,2,1,1,1,2,2],众数是2
public static void main(String[] args) {
int[] nums = {2,2,1,1,1,2,2};
System.out.println("众数是:" + findMajorityElement(nums)); // 输出2
}
整个过程没有额外数组,空间O(1),遍历数组两次(投票+验证),时间O(n),比统计法省内存还快,新手也能一次写对~
避坑提醒:新手常踩的2个“投票陷阱”
1. 忘了验证,把“假候选”当众数:比如数组`[1,2,3]`,投票后候选是3,但3只出现1次(没超1.5),要是不验证直接返回3,就错了!记住:**投票法只保证“有众数时,候选是众数”,没众数时得靠验证筛掉**,就像“班级没超半数,不能硬凑班长”。
2. 初始候选没设对,从索引1开始:比如数组`[3,2,3]`,要是初始候选设成nums[1](2),票数1,后面遇3减到0,再遇3设候选3,最后也能对,但规范是从nums[0]开始,避免数组只有1个元素时出错(比如`[5]`,从nums[0]开始直接返回5,从1开始会越界)。
互动时间:来测测你的“投票实战力”!
1. 数组`[3,2,3]`用摩尔投票法,投票过程的候选和票数变化是啥?最后验证结果是啥?(提示:初始3→票数1,遇2→0,遇3→候选3、票数1,验证3出现2次超1.5,是众数)
2. 要是数组`[1,1,2]`(没众数),投票后候选是啥?验证后会返回啥?(提示:候选=2,验证出现1次没超1.5,返回-1)
3. 你第一次找众数时,有没有用“统计法”写了一堆代码?有没有算错次数的经历?评论区说说你的“数豆子血泪史”!
评论区交出你的答案,前3名答对的送“Java数组算法手册”(含众数、第k大、交集的通俗讲解+代码模板)!关注我,下期揭秘“如何找数组里出现次数最多的元素(不一定超一半)”——比众数多一步,面试常考,实用到爆~