題目描述:
數組中有一個數字出現的次數超過數組長度的一半,請找出這個數字。例如輸入一個長度爲9的數組{1,2,3,2,2,2,5,4,2}。由於數字2在數組中出現了5次,超過數組長度的一半,因此輸出2。如果不存在則輸出0。
解題思路:
方法一:
數組中某個數字出現的次數超過了數組長度的一半,我們把它叫做衆數;如果將數組排序,衆數必然出現在數組索引爲middle = numbers.size() >> 1的位置;所以想到對數組進行排序,排序算法中最常用的就是複雜度O(nlogn)的快速排序算法,排序算法的核心操作(partion)就是隨機選擇數組中的一個數值元素作爲分界值,將小於該數值的元素放在它的左邊,將不小於該數值的元素放在它的右邊;如果隨機選擇的這個數值元素出現的位置恰好在middle位置,那麼該數值則爲衆數(因爲它的其那面的數組元素比它小,後面的數組元素不小於它,並且它恰好在數組中間位置,它就是這個數組的中位數,也是衆數);如果隨機選擇的這個數值元素出現的位置大於middle位置,那麼衆數在該數值元素的前面,對該位置之前的元素再進行一次partion;如果隨機選擇的這個數值元素出現的位置小於middle位置,那麼衆數在該數值元素的後面,對該位置之後的元素再進行一次partion;類似於二分查找,直到隨機選擇的數值元素出現的位置等於middle位置。
方法二:
首先設置兩個輔助變量,result保存數組中的一個元素值,初始爲數組的第一個元素,cout表示次數,初始爲1,表示result出現了一次;接下來遍歷數組,如果下一個元素與result相等,則將cout 加1,如果不等則將cout減一,當cout爲零時,將當前位置的數字元素保存到result中,並將cout設置爲1,表示該數字出現了一次;遍歷數組結束,最後一次使cout爲零時對應的元素就是衆數的數值,及此時result中的數值。
爲什麼???當遍歷數組到最後一次使cout爲零對應的元素時,前面的衆數數值元素與非衆數值元素出現了相同的次數,才能使cout爲零;如果此刻的元素不是衆數,則cout必將再次爲零,因爲該位置之後的衆數元素個數大於非衆數元素個數,所以與最後一次使cout爲零這個前提條件矛盾,所以它必然是衆數。
注意事項:
1)判斷輸入數組是否爲空,是否存在衆數;
2)方法一移動了輸入數組部分元素的位置,方法二則沒有;時間複雜度方面,兩種方法都是O(n)的,方法一的時間複雜度說明參見《算法導論》相關章節;由此可見推薦使用方法二。
通過代碼(c++):
方法一:
#include<iostream>
#include<random>
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
if(numbers.empty())
return 0;
int middle = numbers.size() >> 1;
int start = 0;
int end = numbers.size() - 1;
int index = partion(numbers,start,end);
if(index < 0)
return 0;
while(index != middle)
{
if(index > middle)
{
end = index-1;
}
else
{
start = index+1;
}
index = partion(numbers,start,end);
}
//判斷輸入是否存在超過數組長度一半的數
int result = numbers[index];
if(!checkMoreThanHalf(numbers,result))
return 0;
return result;
}
//快速排序的基礎,返回選中的基準值在數組中的位置
int partion(vector<int>&numbers, int start, int end)
{
int len = numbers.size();
if(numbers.empty() || start < 0 || end >= len)
return -1;
//產生start-end的隨機數
int index = RandomInRange(start,end);
swap(numbers[index],numbers[end]);
int small = start - 1;
for(index = start; index < end; ++index)
{
if(numbers[index] < numbers[end])
{
++small;
if(small != index)
swap(numbers[small],numbers[index]);
}
}
++small;
swap(numbers[small],numbers[end]);
return small;
}
//產生[start,end]的隨機數
int RandomInRange(int start,int end)
{
std::mt19937 rng;//隨機數類型
rng.seed(std::random_device()());//初始化隨機數種子
//創建一個均勻分佈,這個均勻分佈可以等概率(隨機)生成[start,end]區間的整形數字;
std::uniform_int_distribution<int> dist(start,end);
return dist(rng);
}
//判斷輸入是否存在超過數組長度一半的數
bool checkMoreThanHalf(vector<int>&numbers,int result)
{
int cout = 0;
int len = numbers.size();
for(int i=0; i<len; ++i)
{
if(numbers[i] == result)
cout++;
}
if(cout * 2 > len)
return true;
return false;
}
};
方法二:
#include<iostream>
#include<random>
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
if(numbers.empty())
return 0;
int result = numbers[0];
int len = numbers.size();
int cout = 1;
for(int i = 0; i < len-1; ++i)
{
if(numbers[i+1] == numbers[i])
{
cout++;
}
else
{
cout--;
if(cout == 0)
{
result = numbers[i+1];
cout = 1;
}
}
}
//判斷輸入是否存在超過數組長度一半的數
if(!checkMoreThanHalf(numbers,result))
return 0;
return result;
}
//判斷輸入是否存在超過數組長度一半的數
bool checkMoreThanHalf(vector<int>&numbers,int result)
{
int cout = 0;
int len = numbers.size();
for(int i=0; i<len; ++i)
{
if(numbers[i] == result)
cout++;
}
if(cout * 2 > len)
return true;
return false;
}
};