zoukankan      html  css  js  c++  java
  • 统计出现次数超过一半的数字

    出现次数超过一半的数字

    题目描述

    题目:数组中有一个数字出现的次数超过了数组长度的一半,找出这个数字。

    分析与解法

    一个数组中有很多数,现在我们要找出其中那个出现次数超过总数一半的数字,怎么找呢?大凡当我们碰到某一个杂乱无序的东西时,我们人的内心本质期望是希望把它梳理成有序的。所以,我们得分两种情况来讨论,无序和有序。

    解法一

    如果 无序 ,那么我们是不是可以先把数组中所有这些数字 先进行排序 (至于排序方法可选取最常用的快速排序)。排完序后,直接遍历,在遍历整个数组的同时统计每个数字的出现次数,然后把那个出现次数超过一半的数字直接输出,题目便解答完成了。总的时间复杂度为O(nlogn + n)。

    但 如果是有序的数组呢 ,或者经过排序把无序的数组变成有序后的数组呢?是否在排完序O(nlogn)后,还需要再遍历一次整个数组?

    我们知道,既然是数组的话,那么我们可以根据数组索引支持直接定向到某一个数。我们发现,一个数字在数组中的出现次数超过了一半,那么在已排好序的数组索引的N/2处(从零开始编号),就一定是这个数字。自此,我们只需要对整个数组排完序之后,然后直接输出数组中的第N/2处的数字即可,这个数字即是整个数组中出现次数超过一半的数字,总的时间复杂度由于少了最后一次整个数组的遍历,缩小到O(n*logn)。

    然时间复杂度并无本质性的改变,我们需要找到一种更为有效的思路或方法。

    解法二

    既要缩小总的时间复杂度,那么可以用查找时间复杂度为O(1)的 hash表 ,即以空间换时间。哈希表的键值(Key)为数组中的数字,值(Value)为该数字对应的次数。然后直接遍历整个 hash表 ,找出每一个数字在对应的位置处出现的次数,输出那个出现次数超过一半的数字即可。

    解法三

    Hash表需要O(n)的空间开销,且要设计hash函数,还有没有更好的办法呢?我们可以试着这么考虑,如果 每次删除两个不同的数 (不管是不是我们要查找的那个出现次数超过一半的数字),那么,在剩下的数中,我们要查找的数(出现次数超过一半)出现的次数仍然超过总数的一半。通过不断重复这个过程,不断排除掉其它的数,最终找到那个出现次数超过一半的数字。这个方法,免去了排序,也避免了空间O(n)的开销,总得说来,时间复杂度只有O(n),空间复杂度为O(1),貌似不失为最佳方法。

    举个简单的例子,如数组a[5] = {0, 1, 2, 1, 1};

    很显然,若我们要找出数组a中出现次数超过一半的数字,这个数字便是1,若根据上述思路4所述的方法来查找,我们应该怎么做呢?通过一次性遍历整个数组,然后每次删除不相同的两个数字,过程如下简单表示:

    0 1 2 1 1 =>2 1 1=>1 

    最终1即为所找。

    但是数组如果是{5, 5, 5, 5, 1},还能运用上述思路么?很明显不能,咱们得另寻良策。

    解法四

    咱们根据数组的特性考虑到:

    • 数组中有个数字出现的次数超过了数组长度的一半。换句话说,有个数字出现的次数比其他所有数字出现次数的和还要多。
    • 因此我们可以考虑在遍历数组的时候保存两个值:一个是数组中的一个数字,一个是次数,当我们遍历到下一个数字的时候:
      • 如果下一个数字和我们之前保存的数字相同,则次数加1;
      • 如果下一个数字和我们之前保存的数字不同,则次数减1,我们需要保存下一个数字,并把次数重新设为1。

    下面,举两个例子:

    • 第一个例子,假定数组为{5, 5, 5, 5, 1}

      不同的相消,相同的累积。遍历到第四个数字时,candidate 是5, nTimes 是4;遍历到第五个数字时,candidate 是5, nTimes 是3;nTimes不为0,那么candidate就是超过半数的。

    • 第二个例子,假定数组为{0, 1, 2, 1, 1}

      开始时,保存candidate是数字0,nTimes为1;遍历到数字1后,与数字0不同,则nTimes减1变为零;接下来,遍历到数字2,2与1不同,candidate保存数字2,且nTimes重新设为1;继续遍历到第4个数字1时,与2不同,nTimes减1为零,同时candidate保存为1;最终遍历到最后一个数字还是1,与我们之前candidate保存的数字1相同,nTimes加1为1。最后返回的是之前保存的candidate为1。

    思路清楚了,完整的代码如下:

    //a代表数组,length代表数组长度 int FindOneNumber(int* a, int length) { int candidate = a[0]; int nTimes, i; for (i = nTimes = 0; i < length; i++) { if (candidate == a[i]) nTimes++; else nTimes--; if (nTimes == 0) { candidate = a[i]; nTimes = 1; } } return candidate; } 

    即针对数组{0, 1, 2, 1, 1},套用上述程序可得:

    i=0,candidate=0,nTimes=1; i=1,a[1] != candidate,nTimes--,=0; i=2,candidate=2,nTimes=1; i=3,a[3] != candidate,nTimes--,=0; i=4,candidate=1,nTimes=1; 如果是0,1,2,1,1,1的话,那么i=5,a[5] == candidate,nTimes++,=2;...... 

    举一反三

    加强版水王:找出出现次数刚好是一半的数字

    分析:我们知道,水王问题:有N个数,其中有一个数出现超过一半,要求在线性时间求出这个数。那么,我的问题是,加强版水王:有N个数,其中有一个数刚好出现一半次数,要求在线性时间内求出这个数。

    因为,很明显,如果是刚好出现一半的话,如此例: 0,1,2,1 :

    遍历到0时,candidate为0,times为1 
    遍历到1时,与candidate不同,times减为0 
    遍历到2时,times为0,则candidate更新为2,times加1 
    遍历到1时,与candidate不同,则times减为0;
    我们需要返回所保存candidate(数字2)的下一个数字,即数字1。
    转:http://frank19900731.github.io/ebook/the-art-of-programming-by-july/04.03.html
  • 相关阅读:
    安装好k8s后,使用kubectl get cs 查看状态,发现 controller-manager scheduler Unhealthy
    k8s 去掉node标签 难去的
    项目中常用到的布局 flex
    scanf输入空格
    sed和awk学习第一天
    线程池
    线程间通信 | 等待唤醒机制
    创建线程的三种方法详细对比
    EXCEL中VLOOKUP函数的使用
    Vulnhub实战靶场:GAARA: 1
  • 原文地址:https://www.cnblogs.com/hirampeng/p/9613336.html
Copyright © 2011-2022 走看看