1. 题目来源
链接:数组中出现次数超过一半的数字
来源:牛客网
2. 题目说明
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
3. 题目解析
如果这个题目是排好序的数组,那么我们就很容易统计出每个数字出现的次数。题目给出的数组没有说是排好序的,因此我们需要先给它排序。 排序的时间复杂度是O(nlogn) 。最直观的算法通常不是面试官满意的算法,我们需要找出更快的算法。剑指offer上有讲该题,提供了一种类似快排的O(n)针对中位数的排序算法。
在此参考多数投票算法的思想:
先随意确定一个候选元素,cnt是候选元素的计数,当遇到一个跟候选元素不同的元素时,两者数量上抵消一个,cnt减1。一旦cnt变成0,就重新找一个候选元素。
当遇到一个与候选元素不同的元素时,就要抵消。
对于候选元素和当前元素,可能存在两种情况:
- 两者中有一个正好是主要元素;
- 两者都不是主要元素。
对于情况1,抵消过后,主要元素还是主要元素;对于情况2,可以说主要的元素的地位得到了巩固。所以算法最终能找到主要元素。
多数投票算法(Majority Vote Algorithm)
在面试题中经常会出现这样一个题目,给一个数组,其中含有N个非负元素,让你求出数组中出现次数超过一半的数字。
看到这个问题我们首先想到的可能是暴力的解法,那就是将数组排个序,输出中间的元素就行了,因为如果出现次数超过一半的话排完序后中间的那个元素肯定是我们需要求的值。
这样做的话排序的时间复杂度一般来说是O(NlogN),那么有没有时间复杂度为n的算法呢?
答案当然是有的,有这样的一个算法,Majority Vote Algorithm,他是这样的做的:设置一个计数器count和保存最多元素的变量majority,
- 如果count==0,则将now的值设置为数组的当前元素,将majority赋值为1;
- 反之,如果majority和现在数组元素值相同,则count++,反之count–;
- 重复上述两步,直到扫描完数组。
- count赋值为0,再次从头扫描数组,如果素组元素值与majority的值相同则count++,直到扫描完数组为止。
- 如果此时count的值大于等于n/2,则返回majority的值,反之则返回-1。
4. 代码展示
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
int n = numbers.size();
if (n == 0)
return 0;
int num = numbers[0], count = 1;
for (int i = 1; i < n; i++) {
if (numbers[i] == num)
count++;
else
count--;
if (count == 0) {
num = numbers[i];
count = 1;
}
}
// 经过上面的操作, 已经找到该数 num 了.
// 下面的操作是为了确认 num 确实是出现次数超过一半.
count = 0;
for (int i = 0; i < n; i++) {
if (numbers[i] == num)
count++;
}
if (count * 2 > n)
return num;
return 0;
}
};