【剑指Offer-时间效率】面试题39:数组中出现超过一半的数字
题目描述
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
思路1
假如数组是排好序的并且存在超过长度(假设为n)一半的数字,那么这个数字肯定是第n/2个数字,也就是说这个数字是排序后数组的中位数。我们可以借助快速排序的partition函数来实现这一算法。partition函数的步骤为:选择一个基准数字(通常选数组的第一个数字),将数组分为两部分,左边都小于基准,右边都大于基准,然后返回分割点的下标。如果分割点的下标小于n/2,那么说明中位数在分割点的右半部分;如果分割点的下标大于n/2,则说明中位数在分割点的左半部分;如果分割点的下标等于n/2,则循环结束。代码如下:
class Solution {
public:
void swap(vector<int>& v, int i, int j){
int t = v[i];
v[i] = v[j];
v[j] = t;
}
int partition(vector<int> v, int start, int end){
int i=start;
int j=end;
int baseline = v[start];
while(i<j){
while(v[i]<=baseline && i<j)
i++;
while(v[j]>=baseline && i<j)
j--;
swap(v, i, j);
}
swap(v, start, i);
return i;
}
int MoreThanHalfNum_Solution(vector<int> numbers) {
if(numbers.empty())
return 0;
int left = 0;
int length = numbers.size()-1;
int right = numbers.size();
int baseline = numbers[0];
int idx = partition(numbers, left, right);
while(idx!=length/2){
if(idx<length/2){
left = idx+1;
idx = partition(numbers, left, right);
}
else if(idx>length/2){
right = idx-1;
idx = partition(numbers, left, right);
}
}
int ans = numbers[idx]; //判断答案的出现次数是否大于数组长度的一半
int times = 0;
for(int i=0; i<numbers.size(); i++){
if(numbers[i]==ans)
times++;
}
if(times>length/2)
return ans;
else return 0;
}
};
该算法的时间复杂度为O(n).
思路2
假如数组中存在出现次数超过数组长度一半的数字,则该数字出现的次数比其他数字出现次数的和还要大。因此,在遍历数组的时候保留两个值:一个是数组中的一个数字,另一个是次数(初始化为1)。当下一个数字和保留的数字相同时,次数加1,;当不同时,次数减1,当次数为0时(说明保留的数字不满足出现次数比其余数字出现的次数和还要大),将保留的数字变为下一个数字,并把次数设为1。这样,由于满足条件的数字出现的次数比其余数字出现次数的和还要大,所以当遍历结束时,最后一次保留的数字就是要求的答案。代码如下:
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
if(numbers.empty())
return 0;
int ans = numbers[0];
int cnt = 1;
for(int i=1; i<numbers.size(); i++){
if(numbers[i]==ans)
cnt++;
else{
cnt--;
if(cnt==0){
ans = numbers[i];
cnt = 1;
}
}
}
int times = 0;
for(int i=0; i<numbers.size(); i++){
if(numbers[i]==ans)
times++;
}
if(times>numbers.size()/2)
return ans;
else return 0;
}
};
这个算法只需要把数组遍历一遍,所以时间复杂度为O(n)。