题目描述
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
分析:
1.我们首先根据数组特性,出现次数超过一半,意味着我们将数组进行排序后,中间位置的数必然是我们要寻找的数,基于这样的想法首先得到这样的解法,时间复杂度O(nlogn)
//数组中有一个数字出现次数超过数组长度的一半,那么从统计意义上说,这个数必然是数组排序后的中位数
//由于先进行了排序 时间复杂度为O(nlogn)
class Solution {
public:
Solution()
{
}
~Solution()
{
}
int MoreThanHalfNum_Solution(vector<int> numbers) {
int len = numbers.size();
if(len<=0) return 0;
sort(numbers.begin(),numbers.end());
int solve = numbers[len/2];
//从左右开始统计
int ans = 0;
for(int i = len/2,j = 0;;j++)
{
if(j==0) {
ans++;
continue;
}
if(i-j>=0&&numbers[i-j]==solve) ans++;
if(i+j<len&&numbers[i+j]==solve) ans++;
if(i-j<0&&i+j>=len) break;
}
if(ans>len/2) return solve;
return 0;
}
};
2.上述解法虽然能够AC,但是时间复杂度为O(nlogn),不够优秀,这时我们能够想到其实可以使用哈希表来统计每个数出现的次数,只需要遍历一遍数组即可,时间复杂度为O(n)
//利用哈希表思路,时间复杂度为O(n)
class Solution {
private:
map<int,int>num;
public:
Solution()
{
num.clear();
}
~Solution()
{
}
int MoreThanHalfNum_Solution(vector<int> numbers) {
int len = numbers.size();
if(len<=0) return 0;
int k = len>>1;
for(int i=0;i<len;i++)
{
num[numbers[i]]++;
if(num[numbers[i]]>k) {
return numbers[i];
}
}
return 0;
}
};
3.接下来我们思考可不可以不开辟额外空间,我们了解到一次快排可以得知当前主元在原数组中所处的位置,同时我们要得到下标为数组长度一半的元素,这样我们使用快排+二分
注意:这里的快排使用了随机化主元的方式,在进行randindex()函数的时候,出现过除0错误,这是由于rand的上界和下界相等造成的,加一个相等的特判即可。
//基于快排+二分的思想
//这种写法是单路快排,更加好的方法三路快排
class Solution {
public:
Solution()
{
}
~Solution()
{
}
int randindex(int l ,int r)
{
if(l==r) return l;
srand(time(NULL));
return (int)rand()%(r-l)+l;
}
int quick_sort(int l, int r,vector<int> &num)
{
//找到主元所在位置 随机化主元
int i = l ;
int j = r;
int index = randindex(l,r);
int temp = num[index];
swap(num[index],num[l]);
while(i!=j)
{
while(i<j&&num[j]>=temp) j--;
while(i<j&&num[i]<=temp) i++;
swap(num[i],num[j]);
}
swap(num[l],num[i]);
return i;
}
int MoreThanHalfNum_Solution(vector<int> numbers) {
int len = numbers.size();
if(len<=0) return 0;
//进行多次快排寻找位于len/2的元素
int l = 0 , r = len-1,solve;
while(l<=r)
{
int index = quick_sort(l,r,numbers);
if(index==len/2) {
solve = numbers[index];
break;
}
else if(index>len/2) r = index-1;
else if(index<len/2) l = index+1;
}
int ans = 0;
for(int i=0;i<len;i++)
if(numbers[i]==solve) ans++;
if(ans>len/2) return solve;
return 0;
}
};
4.最后是三路快排实现的方法,三路快排返回一个区间范围,表示当前主元及与当前主元相等的元素都聚集的一个范围,当中间元素在这个范围内时,则说明我们找到解,之后在进行判断即可,反之,我们对区间再进行二分
//二分+三路快排
class Solution {
public:
Solution()
{
}
~Solution()
{
}
int randindex(int l,int r)
{
srand(time(NULL));
if(l==r) return l;
return rand()%(r-l)+l;
}
//进行三路快排,每次都将重复的主元都放到一起,返回这个区间
pair<int,int> ThreeWaysQuick_sort(int l,int r,vector<int>& vec)
{
//随机化选取主元
int index = randindex(l,r);
int temp = vec[index];
//利用三个指针实现三路快排
//p1指向第一个是temp的数,方便与后面小于temp的数进行交换
//p2指向最后一个是temp的数,一旦发现一个小于temp的数,马上与p1指针指向的数进行交换
//p3指向大于temp的第一个数
int p1 = l , p2 = l ,p3 = r;
while(p2<=p3)
{
if(vec[p2]==temp) {
p2++;
}
else if(vec[p2]<temp) {
swap(vec[p1],vec[p2]);
p1++;
p2++;
}
else if(vec[p2]>temp) {
swap(vec[p3],vec[p2]);
p3--;
}
}
if(vec[p3]==temp) p3++;
return make_pair(p1,p2);
}
int MoreThanHalfNum_Solution(vector<int> numbers) {
int len = numbers.size();
if(len<=0) return 0;
int l = 0 , r = len - 1 , solve;
while(l<=r)
{
//三路快排,每次都将相同的数放到一起,所以得到的是一个当前主元的区间范围,如果返回的区间范围包括了中间值,那么结束二分
pair<int,int> index;
index = ThreeWaysQuick_sort(l,r,numbers);
if(len/2>=index.first && len/2<=index.second) {
solve = numbers[len/2];
break;
}
else if(len/2<index.first) {
r = index.first-1;
}
else if(len/2>index.second) {
l = index.second+1;
}
}
int ans = 0;
for(int i = len/2 , j = 0; ; j++ )
{
if(j==0) {
ans++;
continue;
}
if(i-j>=0) {
if(solve==numbers[i-j]) ans++;
}
if(i+j<len) {
if(solve==numbers[i+j]) ans++;
}
if(i-j<0&&i+j>=len) break;
}
if(ans>len/2) return solve;
return 0;
}
};