问题:给定一个数组,长度为 n,求这个数组里面出现多于 n/ 2 的数字。
可以用分冶的方法解决。如下图。
我们不断地将问题缩减到两个一半(left 和 right)的规模,根据 left 和 right 求出的多于一半的元素,分别为 lResult 和 rResult,如果这两个值相等,那毫无疑问这个元素是所求。如果不相等,则需要在 left 中求 rResult 出现的次数,在 right 中求 lResult 出现的次数,与递归中求得的 lResult 和 rResult 分别的次数相加,看是不是出现了一半以上。
这个算法的复杂度的递推式为:T(n) = 2 * T(n/2) + n。后面加上的一个 n 是最坏的情况下,lResult 和 rResult 不相等,此时,需要在两边独立地求它们出现的次数。即两个 n/2。算出来时间复杂度为 nlgn。因此这个算法效率不高,我们试想,直接使用排序,就可以达到 nlgn 的时间复杂度,再直接取排序后的数组的中间元素,需要验证它是出现次数大于一半的元素(如果有出现次数多于一半的元素,那么中间元素必定是所求)我们需要验证这个结果是不是真的出现了多于一半。因此,我们不需要使用分冶的办法,而直接使用排序算法就可以达到本算法的时间复杂度。但是,因为排序带入了我们不需要的信息,如元素之间大小排序关系,因此, 本问题应该有更简单的解法。
我们在下面给出两种解法,前面一种是本文的算法,后面一种是何海涛的解法(http://zhedahht.blog.163.com/blog/static/25411174201085114733349/),何海涛的实现比较巧妙,算法充分利用了“有个数字出现的次数比其他所有数字出现次数的和还要多”。最终时间复杂度为 O(n)。
给出实现的源代码如下。
#include
#include
#include
#define NOT_FOUND 9999999
//fnum 在长度为 nlength 的数组 nums 中出现了多少次
int appear(int fnum,int *nums,int nlength)
{
int i,ncount = 0;
for (i = 0;i < nlength;++ i)
{
if(fnum == *(nums + i))
{
++ ncount;
}
}
return ncount;
}
//返回长度为 nlength 的数组 nums 出现次数超过一半的元素,其出现的次数放在 appearCounts 中
int findNumMoreThanHalf(int *nums,int nlength,int *appearCounts)
{
if(0 == nlength)
{
return NOT_FOUND;
}
else if(1 == nlength)
{
*appearCounts = 1;
return *nums;
}
else
{
int mid = nlength / 2;
int leftFind,rightFind;
int *lnums = nums,*rnums = nums + mid;
int llength = mid,rlength = mid + (nlength & 1);
int lcounts,rcounts;
leftFind = findNumMoreThanHalf(lnums,llength,&lcounts);
rightFind = findNumMoreThanHalf(rnums,rlength,&rcounts);
if(NOT_FOUND != leftFind && NOT_FOUND != rightFind)
{
if(leftFind != rightFind)
{
int appearInRigth = appear(leftFind,rnums,rlength);
int appearInLeft = appear(rightFind,lnums,llength);
if(appearInRigth + lcounts > nlength / 2)
{
*appearCounts = appearInRigth + lcounts;
return leftFind;
}
else if(appearInLeft + rcounts > nlength / 2)
{
*appearCounts = appearInLeft + rcounts;
return rightFind;
}
else
{
return NOT_FOUND;
}
}
else
{
*appearCounts = lcounts + rcounts;
return leftFind;
}
}
else if(NOT_FOUND == leftFind && NOT_FOUND == rightFind)
{
return NOT_FOUND;
}
else
{
if(NOT_FOUND != leftFind)
{
int appearInRigth = appear(leftFind,rnums,rlength);
if(appearInRigth + lcounts > nlength / 2)
{
*appearCounts = appearInRigth + lcounts;
return leftFind;
}
else
{
return NOT_FOUND;
}
}
else
{
int appearInLeft = appear(rightFind,lnums,llength);
if(appearInLeft + rcounts > nlength / 2)
{
*appearCounts = appearInLeft + rcounts;
return rightFind;
}
else
{
return NOT_FOUND;
}
}
}
}
}
//生成长度为 nlength 的整数数组,每一个元素随机生成,上界为 upperBound(exclusive)
int *randomNums(int nlength,int upperBound)
{
int i ;
int *nums = (int*)malloc(sizeof(int) * nlength);
srand((int)time(NULL));
for (i = 0;i < nlength;++ i)
{
*(nums + i) = rand() % upperBound;
}
return nums;
}
//何海涛的方法http://zhedahht.blog.163.com/blog/static/25411174201085114733349/ -
//返回长度为 nlength 的数组 nums 出现次数超过一半的元素,其出现的次数放在 appearCounts 中
int findNumMoreThanHalf_haitao(int *nums,int nlength,int *appearCounts)
{
int last = NOT_FOUND,counts = 0;
int i;
int verifyCounts = 0;
for (i = 0;i < nlength;++ i)
{
if(last != *(nums + i))
{
if(0 == counts)
{
counts = 1;
last = *(nums + i);
}
else
{
--counts;
}
}
else
{
++ counts;
}
}
if(0 == counts)
{
return NOT_FOUND;
}
//verify
for (i = 0;i < nlength;++ i)
{
if(last == *(nums + i))
{
++ verifyCounts;
}
}
if(verifyCounts > nlength / 2)
{
*appearCounts = verifyCounts;
return last;
}
else
{
return NOT_FOUND;
}
}
//取得当前毫秒数
#include
long long getCurrentMillis()
{
SYSTEMTIME lpsystime;
GetLocalTime(&lpsystime);
return ((lpsystime.wHour * 60 + lpsystime.wMinute) * 60 + lpsystime.wSecond) * 1000 + lpsystime.wMilliseconds;
}
void main()
{
int nlength = 10000000;
int *nums = randomNums(nlength,NOT_FOUND);
// const static int nlength = 10;
// int nums[10] = {3,2,4,5,7,4,4,4,4,4};
int appearCounts;
long long startTime = getCurrentMillis();
int moreThanHalf = findNumMoreThanHalf(nums,nlength,&appearCounts);
long long endTime = getCurrentMillis();
if(NOT_FOUND == moreThanHalf)
{
printf("not found.spend %lld millseconds\r\n",endTime - startTime);
}
else
{
printf("%d appears %d,more than half.spend %lld millseconds\r\n",moreThanHalf,appearCounts,endTime - startTime);
}
startTime = getCurrentMillis();
moreThanHalf = findNumMoreThanHalf_haitao(nums,nlength,&appearCounts);
endTime = getCurrentMillis();
if(NOT_FOUND == moreThanHalf)
{
printf("not found.spend %lld millseconds\r\n",endTime - startTime);
}
else
{
printf("%d appears %d,more than half.spend %lld millseconds\r\n",moreThanHalf,appearCounts,endTime - startTime);
}
system("PAUSE");
}
何海涛的算法要优于我的算法,在理论上和上面的实现中的测试都说明了这一点。
本文探讨了在长度为n的数组中找到出现次数超过n/2的元素的问题,提出了两种解决方案:一是利用分治策略,时间复杂度为nlgn;二是采用何海涛提出的优化算法,时间复杂度为O(n),并提供了C语言实现。
168万+

被折叠的 条评论
为什么被折叠?



