39 数组中出现次数超过一半的数字(时间效率)

题目描述:

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

 

测试用例:

功能测试(输入的数组中存在一个出现次数超过数组长度一半的数字;输入的数组中不存在一个出现次数超过数组长度一半的数字)

特殊输入测试(输入的数组中只有一个数字;输入nullptr指针)

解题思路:

1) 基于Partition函数的时间复杂度为O(n)的算法:     会修改原来的数组,如果要求不能修改,则不能选择该方法

数组的特性:数组中有一个数字出现的次数超过了数组长度的一半。如果把这个数组排序,那么排序之后位于数组中间的数字一定就是那个出现次数超过数组长度的一半。即长度为n的数组中第n/2大的数字。有成熟的时间复杂度为O(n)的算法得到数组中任意第K大的数字。

思路:

受快速排序的算法的启发。在随机快速排序算法中,我们先在数组中随机选择一个数字,然后调整数组中数字的顺序,使得比选中的数字小的数字都排在它的左边,比选中数字大的数字都排在它的右边。如果选中的数字下标刚好是n/2,那么这个数字就是数组的中位数;如果它的下标大于n/2,那么它的中位数应该位于它的左边,我们可以接着在它的左边部分数组中查找;如果它的下标小于n/2,那么中位数应该位于它的右边,我们可以接着在它的右边部分的数组中查找。

代码:

实现1:使用自己定义的Swap()

class Solution {
public:
    int (vector<int> numbers) {
        //检查输入是否有效:输入为空时
        if(numbers.empty())
            return 0;
        //输入不为空
        //int mid = (numbers.size()+1)>>1;
        int end = numbers.size()-1;
        int begin = 0;
        int mid = (end-begin+1)>>1;
        int index = Partition(numbers, begin, end);

        while(index!=mid){ //不是中位数的时候
            if(index>mid){//中位数在(begin,index)之间
                end = index-1;
                index = Partition(numbers, begin, end);  //begin end 范围不断缩小
                //index = Partition(numbers, begin, index-1);  //error: end没有被更新,下一次迭代执行line22时,会遍历到最后。
            }else{    //不能用两个if,只能执行一个 //if(index<mid)
                begin = index+1;
                index = Partition(numbers, begin, end);
            }
        }
        //判断是否出现多次
        int res = numbers[mid];
        int count =0;
        for(index=0;index<numbers.size();index++)
            if(numbers[index]==res)
                count++;

        //if(count>mid) //不用加等号
        if(count*2<=numbers.size())
            return 0;
            //return res;

        return res;

    }

    //
    int Partition(vector<int> & numbers,int begin,int end){
        if(numbers.empty() || begin<0 || end>=numbers.size() || begin>end)
            return -1; //还是return0??
        int index = randomInRange(begin,end); //随机选择数组中的一个数
        Swap(numbers,index,end); //可以直接调用函数么?取引用!!!
        //int small=-1; //error
        int small=begin-1;
        for(index=begin;index<end;index++){ //遍历一遍数组 从begin开始而不是从0开始。处理后半部分数组时,begin!=0
            if(numbers[index]<numbers[end]){ //小的树放在左边
                small++;
                if(small!=index)
                    Swap(numbers,index,small);
            }
        }
        small++;
        Swap(numbers,end,small);
        return small;
    }
    void Swap(vector<int> & numbers,int a,int b){
        if(a==b) //一定要有判断,否则会报错   数组中只剩1个元素时,会出现begin==end即a==b
            return;
        numbers[a]=numbers[a]+numbers[b];  //a==b时,存储的值会变为2倍
        numbers[b]=numbers[a]-numbers[b];  //a==b时,存储的值变为0
        numbers[a]=numbers[a]-numbers[b];  //a==b结果永远返回0
    }
    int randomInRange(int begin,int end){
        return (rand()% (end-begin+1))+ begin;
    }
};  

代码中注意事项:

1. 自己实现的swap函数,由于是对数组的元素进行交换,且没有使用临时变量,当对一个数组元素自己与自己交换时(a==b)无论是什么值,最后都会为0。因为最后计算的都是自己减自己。

因此swap要加上判断,当a==b时,不执行交换。

当数组中只剩下一个元素时,begin==end,此时交换时,a==b

2. 产生随机数

要取得[a,b)的随机整数,使用(rand() % (b-a))+ a;

  要取得[a,b]的随机整数,使用(rand() % (b-a+1))+ a;

  要取得(a,b]的随机整数,使用(rand() % (b-a))+ a + 1;

  通用公式:a + rand() % n;其中的a是起始值,n是整数的范围。

3. 书写Partition函数时,注意,每次传入的范围是[begin,end],两者都在不断的缩小范围,begin只有第一次使用的时候能确定是0,其他调用处不确定是否已经修改。因此在定范围时:small与for循环变量index要用begin初始化,而不是使用0!!!

4. MoreThanHalfNum_Solution函数:

在while循环中要不断的更新end = index-1;与begin = index+1;这样下次调用Partition函数时会会缩小范围。而不是直接传入index-1或者index+1,本次调用的end或者begin范围变小,但下次调用使用end或者begin时,传入并没有减小。因此每次要更新一下。

实现2:使用系统自定义swap函数

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        //检查输入是否有效:输入为空时
        if(numbers.empty())
            return 0;
        //输入不为空
        //int mid = (numbers.size()+1)>>1;
        int end = numbers.size()-1;
        int begin = 0;
        int mid = (end-begin+1)>>1;
        int index = Partition(numbers, begin, end);

        while(index!=mid){ //不是中位数的时候
            if(index>mid){//中位数在(begin,index)之间
                end = index-1;
                index = Partition(numbers, begin, end);  //begin end 范围不断缩小
                //index = Partition(numbers, begin, index-1);  //error: end没有被更新,下一次迭代执行line22时,会遍历到最后。
            }else{    //不能用两个if,只能执行一个 //if(index<mid)
                begin = index+1;
                index = Partition(numbers, begin, end);
            }
        }
        //判断是否出现多次
        int res = numbers[mid];
        int count =0;
        for(index=0;index<numbers.size();index++)
            if(numbers[index]==res)
                count++;

        if(count>mid) //不用加等号
            return res;
        //if(count*2<=numbers.size())  //该判断也可以
            //return 0;
        return 0;
    }

    //
    int Partition(vector<int> & numbers,int begin,int end){
        if(numbers.empty() || begin<0 || end>=numbers.size() || begin>end)
            return -1; //还是return0??
        int index = randomInRange(begin,end); //随机选择数组中的一个数
        swap(numbers[index],numbers[end]);
        //Swap(numbers,index,end); //可以直接调用函数么?取引用!!!
        //int small=-1; //error
        int small=begin-1;
        for(index=begin;index<end;index++){ //遍历一遍数组 从begin开始而不是从0开始。处理后半部分数组时,begin!=0
            if(numbers[index]<numbers[end]){ //小的树放在左边
                small++;
                if(small!=index)
                    swap(numbers[index],numbers[small]);
                    //Swap(numbers,index,small);
            }
        }
        small++;
        swap(numbers[end],numbers[small]);
        //Swap(numbers,end,small);
        return small;
    }

    int randomInRange(int begin,int end){
        return (rand()% (end-begin+1))+ begin;
    }
};

2)使用数组特点,找出时间复杂度为O(n)的算法

数组中有一个数字出现的次数超过长度的一半,即;它出现的次数比其他所有数字出现的次数和还要多。

在遍历数组时保存两个值:一个是数组中的一个数字;另一个是次数。当我们遍历到下一个数字的时候,如果下一个数字和我们之前保存的数字相同,则次数加1;如果下一个数字和我们保存的数字不同,则次数减1。如果次数为零,那么我们需要保存下一个数字,并把次数设为1。由于我们要找的数字出现的次数比其他所有数字出现的次数之和多,那么要找的数字肯定是最后一次把次数设为1时对应的数字。

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        if(numbers.empty())
            return 0;

        int res = numbers[0];
        int count = 1;

        for(int i=1;i<numbers.size();i++){
            if(count==0){ //要先判断!!   三个条件用else连接,因为每次只执行一次即可
                res = numbers[i];
                count++; //当前值有一个
            }else if(numbers[i]==res){
                count++;
            }else{
                count--;
            }
        }
        //检查找到的数字是否出现了n/2次
        count = 0;
        for(int i=0;i<numbers.size();i++){
            if(numbers[i]==res)
                count++;
        }
        if(count*2<=numbers.size())
            return 0;

        return res;
    }
};

3)数组排序后,如果符合条件的数存在,则一定是数组中间那个数。由于涉及到快排sort,其时间复杂度为O(NlogN)并非最优;

使用内置sort函数

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers)
    {
        // 因为用到了sort,时间复杂度O(NlogN),并非最优
        if(numbers.empty()) return 0;

        sort(numbers.begin(),numbers.end()); // 排序,取数组中间那个数
        int middle = numbers[numbers.size()/2];

        int count=0; // 出现次数
        for(int i=0;i<numbers.size();++i)
        {
            if(numbers[i]==middle) ++count;
        }

        return (count>numbers.size()/2) ? middle :  0;
    }
};

4)使用map,增加了空间复杂度。且map操作要log(n),因此算法整体复杂度nlog(n)   不建议使用

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        int n = numbers.size();
        //map 记录出现次数
        map<int, int> m;
        int count;
        for (int i = 0; i < n; i++) {
            count = ++m[numbers[i]];
            if (count > n/2) return numbers[i];
        }
        return 0;
    }
};

  

  

  

原文地址:https://www.cnblogs.com/GuoXinxin/p/10486757.html

时间: 2024-10-02 00:36:43

39 数组中出现次数超过一半的数字(时间效率)的相关文章

【Java】 剑指offer(39) 数组中出现次数超过一半的数字

本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集   题目 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字.例如输入一个长度为9的数组{1, 2, 3, 2, 2, 2, 5, 4, 2}.由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2. 思路 思路一:数字次数超过一半,则说明:排序之后数组中间的数字一定就是所求的数字. 利用partition()函数获得某一随机数字,其余数字按大小排在该数字的左右.若该

剑指OFFER----面试题39. 数组中出现次数超过一半的数字

链接:https://leetcode-cn.com/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/ 思路: cnt记录个数,val记录值:遍历数组,若等于val,则cnt++,否则cnt--,若cnt为0,则重置,最后val即为结果. 代码: class Solution { public: int majorityElement(vector<int>& nums) { int cnt =

面试题五 数组中出现次数超过一半的数字 时间为O(n)

也就是说 该数字出现的次数比其他所有数字出现次数的和还要多. 因此可以保存两个值,一个数字,一个次数. 遍历时 1.如果数字相同,count++ 2.如果count == 0 count = 1 number替换 3.如果不相同 count-- int main(){ int array[] = {3, 2, 3, 1, 3, 4}; int number = array[0], count = 0; for (int i = 1; i < 6; i++){ if (count == 0){ c

剑指offer系列源码-数组中出现次数超过一半的数字

题目1370:数组中出现次数超过一半的数字 时间限制:1 秒内存限制:32 兆特殊判题:否提交:2844解决:846 题目描述: 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字.例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}.由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2. 输入: 每个测试案例包括2行: 第一行输入一个整数n(1<=n<=100000),表示数组中元素的个数. 第二行输入n个整数,表示数组中的每个元素,这n个整数的范围是[1,10

47. 数组中出现次数超过一半的数字[Number appears more than half times]

[题目]:数组中有一个数字出现的次数超过了数组长度的一半,找出这个数字. 例如长度为9的数组{1,2,3,2,2,2,5,4,2}中次数超过了数组长度的一半的数字为2,而长度为8的数组{1,2,3,2,2,2,5,4}则为非法输入. [思路一]:先对数组进行排序,再遍历排序后的数组,统计每个数的次数,出现次数最大的数即为要找的数. 时间复杂度:O(nlgn)+ O(n)= O(nlgn):空间复杂度:O(1). [思路二]:先对数组进行排序,出现次数超过数组长度的一半的数必然是数组中间的那个数.

剑指Offer对答如流系列 - 数组中出现次数超过一半的数字

面试题39:数组中出现次数超过一半的数字 题目描述 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字.例如输入一个长度为9的数组{1, 2, 3, 2, 2, 2, 5, 4, 2}.由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2. 问题分析 大家最容易想到的思路是 数字次数超过一半,则说明排序之后数组中间的数字一定就是所求的数字. 既然是数组,要牵扯到排序,大家一般都会选用经典快速排序或者随机快速排序.随机快速排序由于每次划分的依据是从数组随机选出的,所以数据状况对它

编程算法 - 数组中出现次数超过一半的数字 代码(C)

数组中出现次数超过一半的数字 代码(C) 本文地址: http://blog.csdn.net/caroline_wendy 题目: 数组中有一个数字出现的次数超过数组长度的一半, 请找出这个数字. 1. 使用高速排序(QuickSort)的方法, 把中值(middle)和索引(index)匹配, 输出中值, 并检測是否符合要求. 2. 使用计数方法依次比較. 代码:  方法1: /* * main.cpp * * Created on: 2014.6.12 * Author: Spike */

【剑指offer】Q29:数组中出现次数超过一半的数字

就本题而言,个人觉得练习下partition函数是有必要的,毕竟它是快速排序的核心,是基础性的东西,也是必须要掌握的,至于书中给出的"取巧"性解法,是属于个人思维能力的考察,是一种考虑问题的思路,不是一两个问题就能练就的. partition函数,包括快速排序,是一定要信手拈来的,必须的. import random def MoreThanHalf(array): if len(array) == 0: return 0 start = 0 end = len(array) - 1

数据结构-数组中出现次数超过一半的数字

题目:数字中有一个数字出现的次数超过数组长度的一半,请找出这个数字.例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}.由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2. 分析:首先进行排序,因为大于一半,所以说经过排序之后的数组,如有次数大于的话说明那个数肯定是数组中间的那个数. #include <iostream> using namespace std; void QickSort(int *a,int n,int low,int high){ int i=l