18数组中出现次数超过一半的数

    题目描述:数组中有一个数字出现的次数超过了数组长度的一半,找出这个数字。(出自编程之美)

 

    分析:一个数组中有很多数,现在我们要找出这个数组中那个超过出现次数一半的数字,怎么找呢?

       大凡当我们碰到某一个杂乱无序的东西时,考虑是否能够通过排序来解决问题:

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

    但再想想,排序之后,真的还需要再遍历一次整个数组么?我们发现,一个数字在数组中的出现次数超过了一半,那么在已排好序的数组索引的N/2(从零开始编号),就一定是这个数字。自此,我们只需要对整个数组排完序之后,然后直接输出数组中的第N/2处的数字即可,这个数字即是整个数组中出现次数超过一半的数字,总的时间复杂度由于少了最后一次整个数组的遍历,缩小到O(N*logN)

 

    但是不论是上述思路一的O(N*logN+N),还是思路二的O(N*logN),时间复杂度并无本质性的改变。我们需要找到一种更为有效的思路或方法。

    我们可以试着这么考虑,如果每次删除两个不同的数(不管是不是我们要查找的那个出现次数超过一半的数字),那么,在剩下的数中,我们要查找的数(出现次数超过一半)出现的次数仍然超过总数的一半。

    证明:如果x代表数组中出现次数超过一半的数(记为A)的出现总次数,y代表数组元素总个数。则2x>y。分两种情况讨论:删除的2个不同的数中,都是非A,那么删除之后,x=x,y=y-2, 此时,2x > y-2成立。如果删除的2个不同的数,一个是A,一个是非A,则x=x-1, y=y-2, 此时,2(x-1)>y-2成立。得证。

    通过不断重复这个过程,不断排除掉其它的数,最终找到那个出现次数超过一半的数字。这个方法,免去了上述排序过程,总得说来,时间复杂度只有O(N),空间复杂度为O(1),不失为最佳方法。

 

    可以考虑在遍历数组的时候保存两个值:一个是数组中的一个数字res,一个是次数time。

    初始情况res=a[0], time=1,如果下一个数字a[1]和我们之前保存的数字res不同,则次数减1。time=0,则表示将a[0]和a[1]都删除了,置res为下一个数,即a[2], time=1。

    如果下一个数字a[1]和我们之前保存的数字res相同,则次数加1。time=2,则表示将a[0]和a[1]都不能删除,继续扫描数组。

 

    如果扫描当前数时,time=0,表示之前的数都删除了,置当前数为第一个数,time=1.

    如果当前数与res相同,则time++,表明res目前出现了time次。

    如果当前数与res不同,则time--, 如果time不为0,说明删除了当前数与之前保存的res中的一个。如果time为0,表示之前的数都删除了,置当前数为第一个数,time=1.

代码如下:

void morethanhalf(int *set, int len)

{

         int res, time;

         int i, j;

 

         for(i = time = 0; i < len;i++)

         {

                  if(time== 0)

                  {

                          res= set[i];

                          time= 1;

                  }

                  else

                  {

                          if(set[i]== res)time++;

                          elsetime--;

                  }

         }

         //判断输入数组中,是否包含出现次数大于一半的数

         for(i = 0; i < len; i++)

         {

                  if(set[i] == res)

                  {

                          time++;

                  }

         }

 

         if(time> len/2)

         {

                  printf("thenumber is %d ", res);

         }

         else

         {

                  printf("thereis no number times more than half ");

         }

}

 

    扩展:数组中有一个数字出现的次数正好是数组长度的一半,找出这个数字。

    分析:继续采用上面的思想,每次删除2个不同的数,那么最后剩下的2个数中,一定有一个是要找的数。

    只要在上述代码的基础上,继续判断res出现的次数是否为数组长度的一半。如果是,则res就是要找的数,如果不是,那么a[n-1]就是要找的数。

代码如下:

int halfofset(int *set, int len)

{

         int res, time;

         int i, j;

 

         for(i = time = 0; i <len; i++)

         {

                  if(time == 0)

                  {

                          res =set[i];

                          time = 1;

                  }

                  else

                  {

                          if(set[i]== res)time++;

                          elsetime--;

                  }

         }

 

         time = 0;

         for(i = 0; i < len; i++)

         {

                  if(set[i] ==res)time++;

         }

 

         if(time ==len/2)return res;

         return  set[len-1];

}

 

 

(http://blog.csdn.net/v_july_v/article/details/6890054)

原文地址:https://www.cnblogs.com/gqtcgq/p/7247169.html