[数学 求众数] 169. 多数元素 229. 求众数 II (哈希表、摩尔投票法)

169. 求众数 I(寻找一个出现次数 > n/2的元素)

题目链接:https://leetcode-cn.com/problems/majority-element/


分类:

  • 数学(众数、抽屉原理、摩尔投票法)
  • 哈希表(key = 元素值,value = 出现次数)

在这里插入图片描述

思路1:数组排序 + 中位数=众数 (O(NlogN), O(logN))

Arrays.sort()的时间复杂度为O(NlogN),空间复杂度为O(logN).

class Solution {
    public int majorityElement(int[] nums) {
        Arrays.sort(nums);
        return nums[nums.length/2];
    }
}

思路2:哈希表 O(N),O(N)

创建一个哈希表,key=数字,value=出现的次数,遍历完整个数组,完成map的填充之后,再遍历一次map,找出value最大的key,即为众数。

  • 时间复杂度O(N)
    空间复杂度O(N)

思路3:分治法 O(NlogN),O(logN)

一个数如果是数组的众数,则把数组分成大小相同的两部分后,该众数在至少其中一个部分里仍然是众数。

可以用反证法证明:
设一个众数x既不是左半部分的众数也不是右半部分的众数,设左半部分的大小为left,右半部分的大小为right,该数字x的数量 < left/2,也 < right/2,则数字 x 的数量 < (left+right)/2,则数字x不是众数,与假设的前提矛盾,所以这个结论是正确的。

根据这个结论,对数组进行二分,分别找出划分的两部分里的众数,如果得到的两个众数相等,则该数字就是最终的众数;如果两个众数不相等,则两者之中必有一个是众数,所以对这两个数做众数判断,找出其中的众数。

其中,对一个数做众数判断就是遍历整个数组,判断它出现的次数是否 > 数组大小的一半。

  • 时间复杂度:O(NlogN),二分的时间复杂度为O(logN),众数判断需要遍历整个数组,统计目标数字出现的次数,所以时间复杂度为O(N),整合起来整体时间复杂度为O(NlogN))

    空间复杂度:O(logN),递归用到的系统栈,也可以改用迭代,可以把空间复杂度降为O(1).

思路4:摩尔投票法 O(n),O(1)

分析:遍历数组,设数组的第一个数字num,创建一个计数器times表示num的出现次数,置初值times=1;

在遍历过程中,每遇到值相同的元素,times+1,遇到值不同的数字,times-1,当times=0时,num更换为当前的数字,times重新置1,继续遍历。
在遍历结束后,num就是要求的数字。

上面的算法使用到了一个结论:每次从序列里选择两个不相同的数字删除掉(或称为“抵消”),最后剩下一个数字或几个相同的数字,就是出现次数大于总数一半的那个。

证明:

首先,可以证明最终不会一个数字都不剩。

原因: 假设两两抵消之后,最终一个数字都不剩。那么就是说一共有偶数个数字,假设有n个,那么n = 2k,k是整数。所以是进行了k次两两抵消。又因为一定存在众数 (数量超过⌊n/2⌋ = k的数字 ),所以该众数出现次数至少为k+1。由抽屉原理,这就会导致前面两两抵消的某一对数字是一样的。这是矛盾的。所以这就证明了最终不会一个数字都不剩,至少剩下一个。

假设最终剩下的那一种数字是a,假设前面进行了k次两两抵消。要证明a是欲求的众数,即证明其他数字不可能是众数。由于抽屉原理,在前面抵消的数字中,同一种数字最多出现k次,即是除了a之外的数字最多出现k次。而且最终至少剩下一个数字,所以数字的总数量大于等于2k+1。那么除了a之外的数字出现的频率<= k/(2k+1) < k/2k = 1/2,所以证明了除了a之外的数字均不会是众数。那么就是说最终剩下的那种数字a是所求众数。

链接:https://www.zhihu.com/question/49973163/answer/477886752

class Solution {
    public int majorityElement(int[] nums) {
        //特殊用例
        if(nums.length == 1) return nums[0];
        //变量创建和初始化
        int num = nums[0], times = 1;
        //遍历数组
        for(int i = 1; i < nums.length; i++){
            if(nums[i] == num)
                times++;
            else{
                times--;
                if(times == 0){
                    num = nums[i];
                    times = 1;
                }
            }
        }
        return num;
    }
}

229. 求众数 II (寻找所有出现次数 > n/3的元素)

题目链接:https://leetcode-cn.com/problems/majority-element-ii/


分类:

  • 数学(众数问题的变形、摩尔投票法的一般形式)
  • 哈希表(key = 元素值,value = 出现次数)

思路1:哈希表

使用一个哈希表,key=元素值,value=元素出现的次数,每有一个元素对应的value>n/3,就将其加入最终的解集。

class Solution {
    public List<Integer> majorityElement(int[] nums) {
        Map<Integer, Integer> map = new HashMap<>();
        List<Integer> res = new ArrayList<>();
        for(int num : nums){
            int value = map.getOrDefault(num, 0);
            if(value + 1 == nums.length / 3 + 1) res.add(num);//取等号避免重复加入
            map.put(num, value + 1);
        }
        return res;
    }
}
  • 时间复杂度:O(N)
  • 空间复杂度:O(N)

思路2:摩尔投票法

首先,我们可以预先得出数组中满足条件的众数数量:
假设数组存在x个出现次数>n/3的元素,如果x==3,则这三个元素的总出现次数 > n,不可能,所以x < 3.所以 出现次数 >n/3 的元素个数 <= 2.

我们可以使用两个变量p1,p2来存放可能的众数,count1,count2来记录这两个众数的出现次数,初始值count1=count2=0。

实现上需要对169题的摩尔投票法做变形:

例如:[A, B, C, A, A, B, C]

最开始拿前三个元素A,B,C比较,因为三个元素都不相等,所以互相抵消;

拿A,A,B比较,因为存在两个元素相等,所以令p1=A,count1=2,p2=b,count2=1;
在p1,p2被赋值后,后面每次只拿一个元素来比较,取C和p1,p2比较,都不相等,所以count1-1=1,count2-1=0。

数组遍历结束,p1对应的count1>0,p2对应的count2==0,但还不能确定p1就一定是众数,因为可能存在如:[A,B,C,D,E,F,G,G,F],最后剩下p1=G,count1=2,p2=F,count2=1,但这两个变量都不是满足条件的众数,所以需要再遍历一次数组做众数校验,用最朴素的方法检验可能的众数是否真的是众数,检验部分的时间复杂度为O(N),投票部分的时间复杂度为O(N),整体时间复杂度为O(2N)=O(N)。

为了代码更容易实现,我们在初始时就令p1=nums[0],p2=nums[0],然后从数组的第0个元素开始遍历,每次拿nums[i]和p1,p2比较,这样每次只需要从数组中提取一个元素和p1,p2比较:

  • 如果该元素和p1或p2相等,则对应的count+1;
  • 如果都不相等,则先判断当前count的值:
    • 如果存在count == 0,就拿该元素覆盖对应的p,再重新置count = 1;
    • 如果两个count > 0,则两个count都-1。
      需要注意的是,上述四个分支都是互斥的,一次只能进入一个分支。
class Solution {
    public List<Integer> majorityElement(int[] nums) {
        List<Integer> res = new ArrayList<>();
        if(nums == null || nums.length == 0) return res;
        int p1 = nums[0], p2 = nums[0], count1 = 0, count2 = 0;
        for(int num : nums){
            if(p1 == num){
                count1++;
                continue;
            }
            if(p2 == num){
                count2++;
                continue;
            }
            if(count1 == 0){
                p1 = num;
                count1++;
                continue;
            }
            if(count2 == 0){
                p2 = num;
                count2++;
                continue;
            }
            count1--;
            count2--;
        }
        //最后检验p1,p2是不是众数
        count1 = 0;
        count2 = 0;
        for(int num : nums){
            if(num == p1) count1++;
            else if(num == p2) count2++;
          
        }
        if(count1 > nums.length / 3) res.add(p1);
        if(count2 > nums.length / 3) res.add(p2);
        return res;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值