题目
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
思路
一:
利用排序,将数组以从小到大的顺序排序,那这个超过一半的数字就是这个序列的中位数,位于数组中间位置。 找到的中位数就是我们想要的结果。但是排序如果直接用排序算法,则效率为O(nlgn)。可以借鉴快排的思想,取数组中一个数,将比这个数小的数放于它的左边, 比它大的数放于右边。 如果这个数位于数组中间位置,则这个数便是最终的结果。如果这个数位于数组右半部分,则我们继续从左半部分查找中位数;位于左半部分则从右边查找中位数。不断递归,最终确定中位数的位置。
注意:输入的数组要进行检验,判断是否为有效输入,并且要判断是否存在这样一个数。
二:
数组中有一个数字出现的次数超过数组长度的一半,也就是说它出现的次数比其它所有数字出现的次数还要多。 因此我们遍历数组时可以保存两个值:一个是数组中的一个数字,一个是次数。当我们遍历到下一个数字时,如果与之前保存的数字相同,则次数加一;不同,就次数减一。如果次数为零,我们需要保存下一个数字,并把次数设为一。由于我们要找的数字出现的次数比其它所有数字出现的次数之和还要多,那么要找的数字肯定是最后一次把次数置为1时对应的数字。
代码
#include <iostream>
#include <random>
using namespace std;
bool g_bInputInvalid = false;
// 检验输入数组是否为有效
bool CheckInvalidArray(int* numbers, int length)
{
g_bInputInvalid = false;
if (numbers == nullptr || length <= 0)
g_bInputInvalid = true;
return g_bInputInvalid;
}
// 检验数组中数字“number”的个数是否超过一半
bool CheckMoreThanHalf(int* numbers, const int length, const int number)
{
int times = 0;
for (int i = 0; i < length; ++i)
{
if (numbers[i] == number)
times++;
}
bool isMoreThanHalf = true;
if (times << 1 <= length)
{
g_bInputInvalid = true;
isMoreThanHalf = false;
}
return isMoreThanHalf;
}
// 选取一个数字,比选择的数字小的移动到该数字的左边,比选择的数字大的移动到右边。
int Partition(int data[], int length, int start, int end)
{
// 检查输入数据
if(data == nullptr || length <= 0 || start < 0 || end >=length)
throw new std::invalid_argument("Invalid Parameters");
// 使用标准库的随机引擎生成随机下标
std::default_random_engine e;
std::uniform_int_distribution<unsigned> u(start,end);
int index = u(e);
// 将选择的数字移动到数据末尾,方便比较
std::swap(data[index], data[end]);
// small记录上一个比选择的数字要小的数字的下标
int small = start - 1;
for (index = start; index < end; ++index) {
if(data[index] < data[end])
{
++small;
if (small != index)
std::swap(data[index],data[small]);
}
}
++small;
std::swap(data[small],data[end]);
// 返回选择的数字的下标
return small;
}
// 寻找数组中个数超过数组一半的数字
int MoreThanHalfNum(int* numbers, int length)
{
if(CheckInvalidArray(numbers, length))
return 0;
int middle = length >> 1; // 数组排序后,中位数的位置
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, end);
}
else
{
start = index + 1;
index = Partition(numbers, length, start, end);
}
}
// 中位数就是数组中个数超过一半的数字
int result = numbers[index];
// 如果中位数的个数没有超过数组的一半,则返回0
if (!CheckMoreThanHalf(numbers, length, result))
result = 0;
return result;
}
// 寻找数组中个数超过数组一半的数字
int MoreThanHalfNum_2(int* numbers, const int length)
{
if (CheckInvalidArray(numbers, length))
return 0;
int result = numbers[0]; // 记录一个数字
int times = 1; // 记录一个数字出现的次数
for (int i = 1; i < length; ++i)
{
// 如果次数为0,则替换为当家数字
if (0 == times)
{
result = numbers[i];
times = 1;
}
// 如果数字相同,则次数加1
else if (result == numbers[i])
times++;
// 如果数字不同,则次数减1
else
times--;
}
if (!CheckMoreThanHalf(numbers, length, result))
result = 0;
return result;
}
int main()
{
int arr_1[] = {2,1,2,2,2,2,2,6,7,8,9,10,12,2,2};
int arr_2[] = {6,1,2,6,6,6,6,6,7,8,9,10,12,6,6};
int arr_3[] = {1,1,2,2,3,3,4,4,5,5,6,6,7,7,8};
cout << "arr_1 more than half number is: " << MoreThanHalfNum(arr_1, sizeof(arr_1) / sizeof(int)) << endl;
cout << "arr_2 more than half number is: " << MoreThanHalfNum(arr_2, sizeof(arr_2) / sizeof(int)) << endl;
cout << "arr_3 more than half number is: " << MoreThanHalfNum(arr_3, sizeof(arr_3) / sizeof(int)) << endl;
cout << endl;
cout << "arr_1 more than half number is: " << MoreThanHalfNum_2(arr_1, sizeof(arr_1) / sizeof(int)) << endl;
cout << "arr_2 more than half number is: " << MoreThanHalfNum_2(arr_2, sizeof(arr_2) / sizeof(int)) << endl;
cout << "arr_3 more than half number is: " << MoreThanHalfNum_2(arr_3, sizeof(arr_3) / sizeof(int)) << endl;
return 0;
}