1、题目描述
找出整数序列出现次数超过一半的数,比如1,2,3,2,2,2,5,4,2序列中,出现次数超过一半的数字为2。对于这个题目,首先最简单的暴力解法是先对数组排序,然后取中位数即可,时间复杂度为O(n*logn)。第二种方式是使用额外的一个map来保存每个元素出现的次数,时间复杂度为O(n),空间复杂度为O(n)。当然这两种方式是不能通过面试滴。
2、解题思路(一)
如果序列中存在次数超过一半的数,则这个数出现的次数比其他所有数字出现的次数的和还要多。我们可以考虑在遍历数组是保存两个值:一个是数组中的一个数字,另一个是次数。当我们开始遍历数组时,如果下一个数和之前保存的数相同,则次数加一,如果不同则次数减一。如果次数减为0,则保存当前数字,次数设置为1。遍历完成之后,我们要找的数字就是最后一次把次数设为1对应的数字。时间复杂度为O(n),代码如下:
#include<iostream>
#include<vector>
using namespace std;
//找出序列中出现次数超过一半的数字
void SurpassHalfNumber(vector<int> input) {
int count = 1;
int index = 1;
for (int i = 1; i < input.size(); i++) {
if (input[i] == input[i - 1])
count++;
else {
count--;
if (count == 0) {
index = i;
count = 1;
}
}
}
cout << "number:" << input[index] << endl;
}
3、解题思路(二)
由于这个数在数组中出现的次数超过一半,因此这个数必然是数组排序后的中位数。因此可以采用基于快速排序划分的思想,先确定一个枢纽值,将这个枢纽值的位置和n/2进行比较,如果两者相同,则找到中位数,直接返回;如果比n/2大,那么在划分后的左半部分继续划分查找;如果比n/2小,那么在划分后的右半部分继续查找。直到找到一个枢纽值它的位置为n/2,时间复杂度为O(n),算法如下:
// 快速排序的一次划分
int Partition(vector<int> &input, int start, int end) {
int temp = input[start];
int low = start, high = end;
while (low < high) {
while (low<high&&input[high]>=temp)high--;
input[low] = input[high];
while (low < high&&input[low] <= temp)low++;
input[high] = input[low];
}
input[low] = temp;
return low;
}
// 找出序列中出现次数超过一半的数字:使用划分的方法
// 找出超过一半的数字相当于找中位数,可以用快排的思想来做
void SurpassHalfNumber2(vector<int> input) {
int start = 0;
int end = input.size()-1;
int pos = Partition(input, start, end);
while (pos != input.size()/2&&start<=end) {
if (pos < input.size()/2) {
start = pos + 1;
pos = Partition(input, start, end);
}
else {
end = pos - 1;
pos = Partition(input, start, end);
}
}
cout << "number:" << input[pos]<<endl;
}
总结:两种解法的时间复杂度均为O(n),但是第二种解法会改变数组元素的位置,如果要求不能改变数组元素的位置,则只能用第一种解法。