題目描述
數組中有一個數字出現的次數超過數組長度的一半,請找出這個數字。例如輸入一個長度爲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;
}
};