《剑指Offer》Java刷题 NO.28 数组中出现次数超过一半的数字(数组、哈希表、在线处理、动态规划)

《剑指Offer》Java刷题 NO.28 数组中出现次数超过一半的数字(数组、哈希表、在线处理、动态规划)

传送门:《剑指Offer刷题总目录》

时间:2020-05-07
一个多月没刷题了,这一个月浑浑噩噩,说是一边在做项目一边在看书,但是效率有多低只有自己清楚,没有目标意识就只能过一天算一天,并且每天都消逝的好快,给自己定个目标,每天至少三道题!冲!


题目:
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
思路:
法一:O(n),O(n)
自己想的是可以用HashMap,key值用来保存数组元素,val值为该元素在数组出现的次数,然后遍历一遍找出个数超过数组长度一半的数字,没有则输出0;
这样时间复杂度为O(n),空间换时间
法二:在线处理算法O(n),O(1)【基于数组特点】
“消解法”首先要想到——如果数组中存在一个数字出现次数超过一半,那它比其他所有数字出现次数之和还要多。如果以这个数字与一个不同的数字配对从数组里两两删除的话,最后剩下的数字如果只有一个,那就是这个数字;如果剩下两个以上且相同,那也只能是这个数字。
将起始次数times设置为1,起始数字result设置为第一个数。不断向右推进,若当前次数为0(说明刚好消解完),将result更新为当前数字(重新开始,times设置为1)。若当前数字与result相同,次数+1;若不同则-1(消解),接下来只要确认result出现的次数是否超过一半即可。
法三:Partation算法 O(n),O(1)
稍微有点麻烦,但主要是思想
要找的数在排好序之后肯定是中位数;反过来不成立(所以最后需要看出现次数)
但是这个地方并不需要真的去排序,只需要中间那个数的左边都比它小,右边都比它大即可,于是利用快速排序中用到的Partation算法,每次随机产生一个坐标将其作为pivot,执行一次partation算法之后返回pivot的坐标,如果不是刚好在中间,就再往两边找;因为每次都是随机产生位置,所以最好情况下时间复杂度为T(N)=T(N/2)+N也就是O(N)
详见注释
法二、三缺点: 需要判断是否真的次数超过一半,因为上述条件是必要条件但不是充分条件,时间效率没有hash表法好


Java代码:

import java.util.HashMap;
        import java.util.Map;

/**
 * @ClassName MoreThanHalfNumSolutio
 * @Discription 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
 * 例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,
 * 因此输出2。如果不存在则输出0。
 * @Author lemon
 * @Date 2020/5/7 10:14
 **/
public class MoreThanHalfNumSolution {
    /**
     * 哈希表法,key存放数组元素,val存放出现次数
     */
    public static int moreThanHalfNumSolutionOne(int [] array) {
        if(array.length <= 0){
            return 0;
        }
        int result = 0;
        Map<Integer,Integer> temp = new HashMap<>(array.length);
        for(int i = 0;i < array.length;i++){
            if(temp.get(array[i]) == null){
                //未出现过就设置val值为1
                temp.put(array[i],1);
            }
            else {
                //出现过就设置val值加一
                temp.put(array[i],temp.get(array[i]) + 1);
            }
            System.out.println(temp.get(array[i]));
            if(temp.get(array[i]) > array.length/2){
                //放完以后顺便判断一下是否出现次数大于数组长度的一半
                result = array[i];
                break;
            }
        }
        return result;
    }

    /**
     *消解法:初始times值设置为1,result设置为array[0],每遇到一个相同的就times++,遇到不同的就times--,当times==0时,
     * 证明刚好消解完,重新开始,更新result为当前元素,times为1,直到遍历完所有元素;那么result是消解完之后剩下的元素
     * 接下来判断其出现次数是否大于数组长度的一半就好了
     */
    public static int moreThanHalfNumSolutionTwo(int [] array){
        int times = 1;
        int result = array[0];
        for(int i = 1;i < array.length;i++){
            if(times == 0){
                //刚好消解完,重新开始
                times = 1;
                result = array[i];
            }
            if(result == array[i]){
                times++;
            }
            else {times--; }
        }
        times = 0;
        //判断result出现次数是否超过数组长度一半
        for(int i = 0;i < array.length;i++){
            if(array[i] == result){
                times++;
            }
        }
        return times > array.length/2 ? result:0;
    }

    /**
     * 要找的数在排好序之后肯定是中位数;反过来不成立(所以最后需要看出现次数)
     * partation算法:执行一次之后左边都比pivot小,右边都比它大
     * 在数组中随机选择一个位置上的元素作为pivot,先藏在最后,然后设置左右两个指针(坐标)。
     * 开始高指针向左移动,如果遇到小于等于中间值的数据,先停下来;切换低指针移动,当低指针
     * 移动到大于等于中间值的时候,高低指针数据互换,重复以上操作
     * 直到左指针等于右指针,此时都指在较大元素处,将之和pivot互换
     */
    public  int moreThanHalfNumSolutionThree(int [] array){
        if(array.length == 0 ){
            return 0;
        }
        int start = 0;
        int end = array.length - 1;
        int middle = array.length >> 1;
        int index = partation(array,start,end);
        //刚好在中间就返回该元素,在中间的左边就往右边找,否则往左边找
        while(index != middle){
            if(index > middle){
                end = index - 1;
                index = partation(array,start,end);
            }
            else if(index < middle){
                start = index + 1;
                index = partation(array,index + 1,end);
            }
        }
        int result = array[middle];
        //中位数不一定会出现次数大于数组一半长度
        int times = 0;
        for(int i = 0;i < array.length;i++){
            if(array[i] == result){
                times++;
            }
        }
        return times > middle ? result:0;
    }

    public void swap(int[] arr,int a,int b){
        int temp;
        temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
    public int partation(int[] arr,int start,int end){
        //产生一个[left,right]范围的随机数,Math.random()是随机选取>= 0.0 且< 1.0 的伪随机 double 值
        int pivotIndex = (int)(start + Math.random()*(end - start + 1));
        //pivot藏到最后
        swap(arr,pivotIndex,end);
        int pivot = arr[end];
        //定义两个指针
        int left = start - 1;
        int right = end;
        while(true){
            //相等时也停下来做交换,这样极端情况数组全是相同元素时能使得每次pivot处于中间位置
            //每次递归长度减半,否则只是N,N-1,N-2...
            while(left < right && arr[++left] < pivot){}
            while(left < right && arr[--right] > pivot){}
            if(left < right){
                swap(arr,left,right);
            }
            else break;
        }
        //最后肯定是left=right并且指向较大的数,将pivot与之互换
        swap(arr,right,end);
        //返回此时pivot的坐标
        return right;
    }

    public static void main(String[] args) {
        int[] a = {4,2,1,4,2,4};
        MoreThanHalfNumSolution moreThanHalfNumSolution = new MoreThanHalfNumSolution();
       System.out.println(moreThanHalfNumSolution.moreThanHalfNumSolutionThree(a));
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值