面试题29:数组中出现次数超过一半的数字
1.题目描述
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。
2.题目分析
解法1:基于 Partition函数的O(n)算法
数组中有一个数字出现的次数超过了数组长度的一半。如果把这个数组排序,那么排序之后位于数组中间的数字一定就是那个出现次数超过数组长度一半的数字。也就是说,这个数字就是统计学上的中位数,即长度为n的数组中第n/2大的数字。
这种算法是受快速排序算法的启发。在随机快速排序算法中,我们先在数组中随机选择一个数字,然后调整数组中数字的顺序,使得比选中的数字小数字都排在它的左边,比选中的数字大的数字都排在它的右边。如果这个选中的数字的下标刚好是n/2,那么这个数字就是数组的中位数。如果它的下标大于n/2,那么中位数应该位于它的左边,我们可以接着在它的左边部分的数组中查找。如果它的下标小于n/2,那么中位数应该位于它的右边,我们可以接着在它的右边部分的数组中查找。这是一个典型的递归过程,可以用如下代码实现:
int MoreThanHalfNum(int* numbers,int length)
{
if(CheckInvalidArray(numbers,length) return 0;
int middle = length >> 2;//相当于length/2,但是length>>2的效率更高
int start = 0;
int end = length - 1;
int index = Partition(numbers,length,start,end);
while(index != middle)
{
if(index > middle)
{
end = index - 1;
index = Partition(numbers,length,start,end0;
]
else
{
start = index + 1;
index = Partition(numbers,length,start,end);
}
}
int result = numbers[middle];
if(!CheckMoreThanHalf(numbers,length,result)) result = 0;
return res;
}
int partition(int data[],int length,int start,int end)
{
if(data == NULL || length < 1 || start < 0 || end >= length)
{
throw new std::exception("Invalid Parameters");
}
int index = RandomInRange(start,end);// 函数RandomInRange用来生成一个在start和end之间的随机数
Swap(&data[index],&data[end]);//Swap的作用是用来交换两个数字
int small = start - 1;
for(index = start;index < end; ++index)
{
if(data[index] < data[end])
{
++ small;
if(small != index)
{
Swap(&data[index],&data[small]);
}
}
}
++ small;
Swap(&data[small],&data[end]);
return small;
}
解法二:根据数组特点找出O(n)的算法
接下来我们从另外一个角度来解决这个问题。数组中有一个数字出现的次数超过数组长度的一半,也就是说它出现的次数比其他所有数字出现次数的和还要多。因此我们可以考虑在遍历数组的时候保存两个值:一个是数组中的一个数字,一个是次数。当我们遍历到下一个数字的时候,如果下一个数字和我们之前保存的数字相同,则次数加1;如果下一个数字和我们之前保存的数字不同,则次数减1。如果次数为零,我们需要保存下个数字,并把次数设为1。由于我们要找的数字出现的次数比其他所有数字
出现的次数之和还要多,那么要找的数字肯定是最后一次把次数设为1时对应的数字(及最后的次数一定为正数)。
下面是这种思路的参考代码;
int MoreThanHalfNum(int* numbers,int length)
{
if(CheckInvalidArray(numbers,length)) return 0;
int result = numbers[0];
itn times = 1;
for(int i = 1;i<length;++i)
{
if(times == 0)
{
result = numbers[i];
times = 1;
}
else if(numbers[i] == result) ++times;
else --times;
}
if(!CheckMoreThanHalf(numbers,length,result)) result = 0;
return result;
}
如果函数的输入参数是一个指针(数组在参数传递的时候退化为指针),就要考虑这个指针可能为NULL。下面的函数
CheckInvalidArray用来判断输入的数组是不是无效的。题目中说数组中有一个数字出现次数超过数组长度的一半,如果输入的数组中出现频率最高的数字都没有达到这个标准那该怎么办?这就是我们定义了一个CheckMoreThanHalf函数的原因。
bool g_bInputInvalid = false;
bool CheckInvalidArray(int8 numbers,int length)
{
g_bInputInvalid = false;
if(numbers == NULL || length <= 0) g_bInputInvalid = true;
return g_bInputInvalid;
}
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;
}
2020.11.19补充
可以先对这个数组进行排序,然后下标为2/n的那个数就是出现次数最多的数。
或者使用随机化算法
int g_num = 0;//用来计算随机算法的执行次数
int Get_Elem(vector<int>& vec)
{
while (1)
{
g_num++;
srand(time(NULL));
int index = rand() % vec.size();//范围为0~n-1
int count = 0;
int ma = vec[index];
for (int x : vec)
{
if (x == ma)
{
count += 1;
}
}
if (count > vec.size() / 2)
{
return ma;
}
}
return -1;
}
哈希:使用map