现在的位置: 首页 > 综合 > 正文

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

2013年12月06日 ⁄ 综合 ⁄ 共 2330字 ⁄ 字号 评论关闭

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

 

分析:这是一道广为流传的面试题,包括百度、微软和Google在内的多家公司都曾经采用过这个题目。要几十分钟的时间里很好地解答这道题,除了较好的编程能力之外,还需要较快的反应和较强的逻辑思维能力。

 

看到这道题,我们马上就会想到,要是这个数组是排序的数组就好了。如果是排序的数组,那么我们只要遍历一次就可以统计出每个数字出现的次数,这样也就能找出符合要求的数字了。题目给出的数组没有说是排好序的,因此我们需要给它排序。排序的时间复杂度是O(nlogn),再加上遍历的时间复杂度O(n),因此总的复杂度是O(nlogn)

 

接下来我们试着看看能不能想出更快的算法。前面思路的时间主要是花在排序上。我们可以创建一个哈希表来消除排序的时间。哈希表的键值(Key)为数组中的数字,值(Value)为该数字对应的次数。有了这个辅助的哈希表之后,我们只需要遍历数组中的每个数字,找到它在哈希表中对应的位置并增加它出现的次数。这种哈希表的方法在数组的所有数字都在一个比较窄的范围内的时候很有效。本博客系列的第13就是一个应用哈希表的例子。不过本题并没有限制数组里数字的范围,我们要么需要创建一个很大的哈希表,要么需要设计一个很复杂的方法来计算哈希值。因此总体说来这个方法还不是很好。

 

前面两种思路都没有考虑到题目中数组的特性:数组中有个数字出现的次数超过了数组长度的一半。也就是说,有个数字出现的次数比其他所有数字出现次数的和还要多。因此我们可以考虑在遍历数组的时候保存两个值:一个是数组中的一个数字,一个是次数。当我们遍历到下一个数字的时候,如果下一个数字和我们之前保存的数字相同,则次数加1。如果下一个数字和我们之前保存的数字不同,则次数减1。如果次数为零,我们需要保存下一个数字,并把次数设为1。由于我们要找的数字出现的次数比其他所有数字出现的次数之和还要多,那么要找的数字肯定是最后一次把次数设为1时对应的数字。

 

基于这个思路,我们不难写出如下代码:

 

复制代码
 1 bool g_bInputInvalid = false;
 2  
 3 //////////////////////////////////////////////////////////////////////////
 4 // Input: an array with "length" numbers. A number in the array
 5 // appear more than "length / 2 + 1" times.
 6 // Output: If the input is valid, return the number appearing more than
 7 // "length / 2 + 1" times. Otherwise, return 0 and set flag g_bInputInvalid
 8 // to be true.
 9 //////////////////////////////////////////////////////////////////////////
10 int MoreThanHalfNum(int* numbers, unsigned int length)
11 {
12     if(numbers == NULL && length == 0)
13     {
14         g_bInputInvalid = true;
15         return 0;
16     }
17  
18     g_bInputInvalid = false;
19  
20     int result = numbers[0];
21     int times = 1;
22     for(int i = 1; i < length; ++i)
23     {
24         if(times == 0)
25         {
26             result = numbers[i];
27             times = 1;
28         }
29         else if(numbers[i] == result)
30             times++;
31         else
32             times--;
33     }
34  
35     // verify whether the input is valid
36     times = 0;
37     for(int i = 0; i < length; ++i)
38     {
39         if(numbers[i] == result)
40             times++;
41     }
42  
43     if(times * 2 <= length)
44     {
45         g_bInputInvalid = true;
46         result = 0;
47     }
48  
49     return result;
50 }
51  
复制代码

 

 在上述代码中,有两点值得讨论:

(1)      我们需要考虑当输入的数组或者长度无效时,如何告诉函数的调用者输入无效。关于处理无效输入的几种常用方法,在本博客系列的第17中有详细的讨论;

(2)      本算法的前提是输入的数组中的确包含一个出现次数超过数组长度一半的数字。如果数组中并不包含这么一个数字,那么输入也是无效的。因此在函数结束前我还加了一段代码来验证输入是不是有效的。


以上转自何海涛博客

 

《编程之美》上也有这样一道题,即“寻找发帖水王”那道题。这道题还有个扩展问题:如果要找的是出现次数超过1/4的3个数,怎么找?

思路1:一个数组

1 struct {
2     int data; // 元素
3     int num; // 出现次数
4 } arr[4];

 



累积4个不同数的个数。当得到一组数时就同时减1,这样一定有一个元素是为0的。如果为0就加入新的不同于其它非0的元素,依次进行,直到全部数据处理完。这样要找的出现次数超过1/4的3个数就是数组中次数不为0的3个元素。

这种方法写起来比较麻烦。

 

思路2:排序。然后根据1/4特性找起来很容易。

思路3:Hashmap统计,但需要额外空间。

 

本文出自:http://www.cnblogs.com/wolenski/archive/2012/07/07/2580735.html

抱歉!评论已关闭.