题目
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
解题思路
方法一:基于Partition函数的时间复杂度为O(n)的算法
这种算法受快速排序算法的启发。在随机快速排序算法中,我们先在数组中随机选择一个数字,然后调整数组中数字的顺序,使得比选中的数字小的数字都排在它的左边,比选中的数字大的数字都排在它的右边。如果这个选中的数字的下标刚好是n/2,那么这个数字就是数组的中位数;如果它的下标大于n/2,那么中位数应该位于它的左边,我们可以接着再它的左边部分的数组中查找;如果它的小标小于n/2,那么中位数应该位于它的右边,我们可以接着在它的右边部分的数组中查找。这是一个典型的递归过程,可以用如下代码实现:
class Solution
{
public:
bool g_bInputInvalid = false;
//主函数,其中Partition在随机快速排序中有其实现
int MoreThanHalfNum(int*numbers, int length)
{
if (CheckInvalidArray(numbers, length))
return 0;
int middle = length >> 1;
int start = 0, end = length - 1,index=Partition(numbers,length,start,end);
while (index != middle)
{
if (index > middle)
{
end = index - 1;
index= Partition(numbers, length, start, end);
}
else
{
start = index + 1;
index = Partition(numbers, length, start, end);
}
}
int result = numbers[index];
if (!CheckMoreThanHalf(numbers, length, result))
{
result =0;
}
return result;
}
//判断该数字是否超过数组长度的一半
bool CheckMoreThanHalf(int *numbers, int length, int number)
{
int times = 0;
for (int i = 0; i < length; ++i)
{
if (numbers[i] == number)
times++;
}
bool isMoreThanHalf = true;
if (times * 2 <= length)
{
//g_bInputInvalid = true;
isMoreThanHalf = false;
}
return isMoreThanHalf;
}
//判断异常函数
bool CheckInvalidArray(int *numbers, int length)
{
g_bInputInvalid = false;
if (!numbers || !length)
g_bInputInvalid = true;
return g_bInputInvalid;
}
};
方法二:根据数组特点找出时间复杂度为O(n)的算法
数组中有一个数字出现的次数超过数组长度的一半,也就是说它出现的次数比其他所有数字出现次数的和还要多。因此我们可以考虑在遍历数组的时候保存两个值:一个是数组的一个数字,一个是次数。当我们遍历到下一个数字的时候,如果下一个数字和我们之前保存的数字相同,则次数加1;如果下一个数字和我们之前保存的数字不同,则次数减1。如果次数为零,我们需要保存下一个数字,并把次数设为1。由于我们要找的数字出现的次数比其他所有数字出现的次数之和还要多,那么要找的数字肯定是最后一次把次数设为1时对应的数字。
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
if(numbers.empty()){
return 0;
}
// 遍历每个元素,并记录次数;若与前一个元素相同,则次数加1,否则次数减1
int result = numbers[0];
int times = 1;
for(int i = 1; i < numbers.size(); ++i){
if(times == 0){
// 更新result的值为当前元素,并置次数为1
result = numbers[i];
times = 1;
}
else if(numbers[i] == result){
times++;
}
else{
times--;
}
}
// 判断result是否符合条件,即出现次数大于数组长度的一半
if (!CheckMoreThanHalf(numbers, length, result))
{
result =0;
}
return result;
};
解法比较
上述两种算法的时间复杂度都是O(n)。基于Parition函数的算法的时间复杂度的分析不是很直观。我们注意到,在第一种解法中,需要交换数组中数字的顺序,这就会修改输入的数组。是不是可以修改输入的数组呢?在面试的时候,我们可以和面试官讨论,让他明确要求。如果面试官说不能修改输入的数组,那就只能采用第二种解法了。