左神讲算法——超级水王问题(详解)

Posted Baret-H

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了左神讲算法——超级水王问题(详解)相关的知识,希望对你有一定的参考价值。

超级水王问题:给你一个数组,出现次数大于数组长度的一半的元素称之为水王数,怎么能快速找到水王数?

内存限制:时间复杂度O(n),额外空间复杂度O(1)——也就是遍历数组的次数为有限次,申请的变量数为有限个

参考链接2021最新左神数据结构算法全家桶

方式一:暴力法

暴力法的思想很简单,用一个hashmap来存放数组中每个元素及其对应的数量,最后遍历hashmap判断有无元素数量大于数组长度一半,有则直接返回水王数,否则返回-1

public static int verify(int[] arr) {
    if (arr == null || arr.length == 0)
        return -1;
    //新建hashmap用于存放数组中每个元素及其对应的数量
    HashMap<Integer, Integer> hashMap = new HashMap<>();
    //遍历数组的每个元素
    for(int num:arr){
        //如果hashmap中包含该元素,则将该元素的数量+1
        if(hashMap.containsKey(num))
            hashMap.put(num,hashMap.get(num)+1);
        //如果hashmap中不包含key,则将该元素的数量置为1
        else
            hashMap.put(num,1);
    }
    //遍历hashmap中每一个entry<k,v>
    for(Map.Entry<Integer,Integer> record:hashMap.entrySet()){
        if(record.getValue()>(arr.length>>1))//这里采用位运算,速度快
            //返回水王数
            return record.getKey();
    }
    return -1;
}

显然,该方法不符合题目的限制,由于使用了hashmap,所以额外的空间复杂度为O(N)


方法二

如何使用有限变量遍历有限次数来完成呢?

思路:从数组第一个元素开始,依次删掉两个不同的数,最后如果有水王数的话,它会剩下来(反过来不成立,也就是剩下来的数不一定是水王数)
image-20210610130624868
如上图所示,依次删除两个不同的元素,相同则不删除,最后只剩下3即是水王数

但请注意,剩下的不一定是水王数,比如:12345,删掉12、34还剩5,但是5不是水王数

这是什么原理呢?我们假设一个数组中有水王数,由于水王数的个数大于所有元素个数的一半,所以如果拿一个非水王数抵消一个水王数,最后还是会剩下水王数;上述删除不同元素的过程就是如此,还可能会存在两个非水王数互相抵消的情况,这样水王数剩下的会更多。

因此我们的算法可以转换为以下两步:

  1. 依次删除数组中两个不同的数
  2. 判断是否有数剩余。如果没有数剩余,则不存在水王;如果有数剩余,我们再遍历一次数组,计算该数的真实的出现次数与数组长度的一半进行比较判断是否为水王数

怎么实现呢?我们只需要两个变量,变量一candidate作为候选用来存放可能的水王数,变量二hp用来存放其剩余个数,按如下规则遍历数组:

  • hp=0 代表没有candidate候选数;hp>0 代表有candidate候选数
  • 如果没有candidate,则设置当前数赋予candidate,即将当前数作为候选数,hp设置为1
  • 如果有candidate,判断当前数是否等于candidate,相等则hp+1,不相等则hp-1

最后判断看hp是否为0,如果不为0,则代表有可能的水王数,再进行计数判断即可

代码实现

public static int waterKing(int[] arr) {
    if (arr == null || arr.length == 0)
        return -1;
    int candidate = 0;
    int hp = 0;
    for (int cur : arr) {
        if (hp == 0) {//没有候选,则将当前数设为候选
            candidate = cur;
            hp++;
        } else if (cur != candidate)//有候选且当前数!=侯选数
            hp--;
        else//有候选且当前数=候选数
            hp++;
    }
    //没有候选数剩余,则没有水王数
    if (hp == 0)
        return -1;
    //计算候选数在数组中的元素个数
    int count = 0;
    for (int num : arr) {
        if (num == candidate)
            count++;
    }
    //如果候选数元素个数>数组长度的一半则返回,否则返回-1
    return count > (arr.length >> 1) ? candidate : -1;
}

以上是关于左神讲算法——超级水王问题(详解)的主要内容,如果未能解决你的问题,请参考以下文章

左神讲算法——二分法及其拓展

左神讲算法——二分法及其拓展

左神讲算法——异或的高级操作(两数交换+经典面试题)

左神讲算法——异或的高级操作(两数交换+经典面试题)

整理左神讲的,用于以后复习

算法一 超级水王问题