《剑指offer》刷题——【时间效率】面试题39:数组中出现次数超过一半的数字(java实现)

这篇博客详细介绍了如何在数组中找到出现次数超过一半的数字。通过三种方法进行解析:1) O(nlogn)的排序统计法;2) 基于Partition函数的O(n)方法,会改变原数组;3) O(n)不修改原数组的解决方案,利用数组特性。每种方法都有清晰的步骤说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、题目描述

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组
{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不
存在则输出0。

二、题目分析

方法一:O(nlogn)

  • 给数组排序
  • 统计出每个数字出现的次数

方法二:基于Partition函数的时间复杂度为O(n)–会修改数组

  • 把数组排序,排序后位于数组中间的数字一定是那个出现次数超过一半的数字,即将问题转化为数组中第n/2大的数字
  • 受快排的启发,O(n)得到数组中第k大的数字
    • 先在数组中随机选择一个数字,然后调整数字的顺序,使得比选中的数字小的数字都排在它的左边,比选中的数字大的数字都排在它的右边
    • 如果选中的数字的下标刚好是 n/2,那么这个数字就是数组的中位数
    • 如果选中的数字的下标大于 n/2,那么中位数应该位于它的左边,接着在它的左边部分的数组中查找
    • 如果选中的数字的下标小于n/2,那么中位数应该位于它的右边,接着在它的右边部分的数组中查找
public class Solution {
    boolean inputInvalid = false;//标记输入无效
    public int MoreThanHalfNum_Solution(int [] array) {
        //判断输入的数组是否无效
        if(CheckInvalidArray(array)){
            return 0;
        }
        
        int mid = array.length >> 1;//中位数下标
        int start = 0;//开始编号
        int end = array.length-1;//末尾编号
        //选取基准元素,将比基准元素小的都放在基准元素左边,比基准元素大的都放在基准元素右边,
        //并返回基准元素的下标
        int index = Partition(array, start, end);
        //找中位数
        while(index != mid){
            //如果选中的数字下标大于N/2,则中位数应该位于左边
            if(index > mid){
                //在左边部分查找,终止索引修改为选中数字下标的前一位置
                end = index-1;
                index = Partition(array, start,end);
            }
            //如果选中的数字下标小于N/2,则中位数应该位于右边
            else{
                //右边部分查找,起始索引修改为选中数字下标的后一位置
                start = index +1;
                index = Partition(array,start, end);
            }
        }
        //如果选中的数字下标等于N/2,则中位数即选中数字
        int result = array[mid];
        //判断数组中出现频率最高的数字是否达到数组长度的一半
        if(!CheckMoreThanHalf(array, result)){
            result = 0;
        }
        return result;
    }
    
    /**
    *判断输入的数组是否无效
    */
    public boolean CheckInvalidArray(int[] array){
        inputInvalid = false;
        if(array==null || array.length<=0){
            inputInvalid = true;
        }
        return inputInvalid;
    }
    
    /**
    * 判断输入数组出现频次最高的数字是否超过数组长度的一半
    */
    public boolean CheckMoreThanHalf(int[] array, int number){
        int times = 0;//统计此数字在数组中出现次数
        boolean isMoreThanHalf = true; //标记当前数字超过数组长度一半
        for(int i=0; i<array.length;i++){
            if(array[i]==number){
                times++;
            }
        }
        //判断是否超过数组长度一半
        if(times*2<=array.length){
            inputInvalid = true;//输入无效
            isMoreThanHalf = false;//未超过一半
        }
        return isMoreThanHalf;
    }
    
    /**
    * 在基准元素左边的元素都小于基准元素,在基准元素右边的元素都大于等于基准元素。
    * 可以使用partition()方法解决以下常见的问题:
    *  1)查找数组中出现次数超过数组长度一半的元素。
    *   eg:MoreThanHalfSize类中的getElementOfMoreThanHalfSizeByPartition方法。
    *  2)查找数组中最小的(或最大的)k个数 或 查找数组中第k小(或第k大)的数
    *     优点:时间复杂度为O(n)
    *     缺点:改变了原数组中元素的位置。
    */
    public int Partition(int[] array, int start, int end){
        if(array==null || array.length<=0 || start<0 || end>=array.length){
            return -1;
        }
        
        int pivotIndex = start; //基准元素的索引
        //交换首尾元素
        swap(array, start, end);//基准元素放在末尾  
        int small = start-1;//基准元素的最终位置的下标
        //遍历,将小于基准的移到左边,大于的移到右边
        for(pivotIndex=start; pivotIndex<end; pivotIndex++){
            //小于基准元素
            if(array[pivotIndex] < array[end]){
                small++;
                //当当前遍历到的元素位置与基准最终位置指针不相等时,交换
                if(small != pivotIndex){
                //交换当前元素与基准的位置
                    swap(array, pivotIndex, small);
                }
            }
        }
        small++;
        //将基准元素放在最终的位置
        swap(array, small, end);
        //返回基准元素
        return small;
    }
    
    /**
    * 交换数组中两个元素
    */
    public void swap(int[] array, int pivotIndex, int curIndex){
        if(pivotIndex!=curIndex){
            int temp = array[pivotIndex];
            array[pivotIndex] = array[curIndex];
            array[curIndex] = temp;
        }
    }
}

方法三:根据数组的特点时间复杂度为O(n)–不会修改输入的数组

  • 数组中有一个数字出现的次数超过数组长度的一半,即就是它出现的次数比其他所有数字出现次数的和还要多
  • 遍历数组保存两个值:1)数组中的一个数字;2)次数
  • 遍历数组:
    • 若次数为0,保存下一个数字,并把次数设为1;
    • 若下一个数字和之前保存的数字相同,则次数加1;
    • 若下一个数字和之前保存的数字不同,则次数减一;
    • 最后一次把次数设为1时对应的数字,即为所求
public class Solution {
    boolean inputInvalid = false;//标记输入无效
    public int MoreThanHalfNum_Solution(int [] array) {
        //检查输入是否无效
        if(CheckInvalidArray(array)){
            return 0;
        }
        
        int result = array[0];//保存数字
        int times = 1;//次数
        //遍历数组
        for(int i=1; i<array.length; i++){
            //如果次数等于0,保存下一个数字,并设次数为1
            if(times==0){
                result = array[i];
                times =1;
            }
            //如果下一个数字和我们之前保存的数字相同,则次数加1
            else if(array[i]==result){
                times++;
            }
            //如果下一个数字和我们之前保存的数字不同,则次数减1
            else{
                times--;
            }
        }
        //判断是否存在超过数组长度一半的数字
        if(!CheckMoreThanHalf(array,result)){
            result =0;
        }
        return result;   
    }
    
    /**
    *判断输入的数组是否无效
    */
    public boolean CheckInvalidArray(int[] array){
        inputInvalid = false;
        if(array==null || array.length<=0){
            inputInvalid = true;
        }
        return inputInvalid;
    }
    
    /**
    * 判断输入数组出现频次最高的数字是否超过数组长度的一半
    */
    public boolean CheckMoreThanHalf(int[] array, int number){
        int times = 0;//统计此数字在数组中出现次数
        boolean isMoreThanHalf = true; //标记当前数字超过数组长度一半
        //遍历数组
        for(int i=0; i<array.length;i++){
            if(array[i]==number){
                times++;
            }
        }
        //判断是否超过数组长度一半
        if(times*2<=array.length){
            inputInvalid = true;//输入无效
            isMoreThanHalf = false;//未超过一半
        }
        return isMoreThanHalf;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值