数组找众数还在数次数?Java这招0空间秒出,比统计快10倍!

数组找众数还在数次数?Java这招0空间秒出,比统计快10倍!

编码文章call10242025-09-27 19:50:267A+A-

你有没有过这种崩溃:要找数组里“出现超一半”的众数,掏出HashMap逐个统计次数,代码写得像绕口令,还得记“元素→次数”的映射,最后算半天发现空间占了一大块?其实找众数根本不用“数豆子”,Java里的摩尔投票法就像“选班长”,靠“对抗抵消”秒出结果,全程只需要两个变量,连额外数组都不用!今天用“班级投票”的段子给你讲透,看完笑到会写代码,下次找众数你也能当“快手”~

先吐槽“统计法”:像数豆子数到眼瞎,还占地方

新手找众数,最爱用“HashMap统计法”:比如数组`[2,2,1,1,1,2,2]`,要找出现超4次的众数,步骤能绕晕自己:

  1. 新建HashMap,遍历数组,遇到2就把次数+1,遇到1也+1;
  2. 遍历完再翻HashMap,看哪个元素次数超一半(7/2=3.5,超3.5即≥4);
  3. 最后找到2是众数,但代码写了十几行,还占了O(n)的空间。

这就像班级选班长,你拿个小本本记“张三3票、李四4票、王五2票”,记到最后本子写满了,还得再算一遍谁超半数——不仅慢,要是数组有100万元素,HashMap能把内存占得像“堆满快递盒”,纯属给自己找罪受!

摩尔投票法的“巧劲”:像选班长靠“对抗”,票数抵消见真章

摩尔投票法的核心逻辑超简单:**众数出现超一半,比所有非众数加起来还多,所以能靠“一对一抵消”赢到最后**。就像班级选班长,众数是“张三”,支持他的人超一半:

- 每有一个人投张三(遇到众数),票数+1;

- 每有一个人投别人(遇到非众数),票数-1;

- 就算中间票数抵消到0,换个候选人,最后赢的还是张三——因为他的支持者最多,抵消不完!

用数组`[2,2,1,1,1,2,2]`举例,一步步看投票法的魔力:

  1. 初始:候选众数=2,票数=1(先暂定2是班长,有1人支持);
  2. 遇2(支持候选):票数=1+1=2;
  3. 遇1(反对候选):票数=2-1=1;
  4. 遇1(反对候选):票数=1-1=0(票数归0,换候选);
  5. 遇1(换候选为1,票数=1);
  6. 遇2(反对候选1):票数=1-1=0(再换候选);
  7. 遇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大、交集的通俗讲解+代码模板)!关注我,下期揭秘“如何找数组里出现次数最多的元素(不一定超一半)”——比众数多一步,面试常考,实用到爆~

点击这里复制本文地址 以上内容由文彬编程网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

文彬编程网 © All Rights Reserved.  蜀ICP备2024111239号-4