查找过半数字
数组中有某个数字出现的次数超过数组长度的一半,请找出这个数字。
最简单直接的方式是排序数组,然后中位数处的数字即为所要查找的数字,但是排序本身时间复杂度太高。
后面介绍一下其他更快的查找方式(设所有数字都非负,若未找到则返回-1)。
基于Partition函数
在快速排序算法中,需要先选择一个数字对数组进行分割(大的放在右侧,否则左侧);若分割后此数字切好在数组中间位置,则为查找的数字。
int toPartition(int[] aryAll_, int nStart, int nEnd)
{
int nTmp = aryAll_[nStart];
while (nStart < nEnd)
{
while (aryAll_[nEnd] > nTmp) --nEnd;
if (nStart >= nEnd) break;
aryAll_[nStart++] = aryAll_[nEnd];
while (aryAll_[nStart] < nTmp && nStart < nEnd) ++nStart;
if (nStart >= nEnd) break;
aryAll_[nEnd--] = aryAll_[nStart];
}
aryAll_[nStart] = nTmp;
return nStart;
}
public int FindViaPartition(int[] arySource)
{
Debug.Assert(arySource != null && arySource.Length > 0);
int nStart = 0;
int nEnd = arySource.Length - 1;
int nMiddle = arySource.Length / 2;
int nIndex = toPartition(arySource, nStart, nEnd);
while(nIndex != nMiddle)
{
if (nIndex < nMiddle)
nStart = nIndex + 1;
else // >
nEnd = nIndex - 1;
nIndex = toPartition(arySource, nStart, nEnd);
}
int nNum = arySource[nIndex];
if (CheckIfMoreThanHalf(arySource, nNum))
return nNum;
return -1;
}
位于中间的数字不一定就是我们所要查找的数字(有可能数组中就没有过半的数字),为此需要统计此数字在数组出现的实际次数来判断:
bool CheckIfMoreThanHalf(int[] arySource, int nNumber)
{
int nCount = 0;
foreach(var n in arySource)
{
if (n == nNumber) nCount++;
}
return (nCount * 2) > arySource.Length;
}
计数方式
对数组中数字从前至后进行计数(记录当前计数的数字curNum与计数curCount),若有超过半数的数字存在,则其最后的计数肯定不为0(也有可能是较后面出现的数字,因此最后还需通过实际计数进行验证):
- 若计数为0则,设当前数字为curNum,且计数为1;否则
- 若当前数字与curNum相同,则增加计数;否则,则减少计数;
public int FindViaCount(int[] arySource)
{
Debug.Assert(arySource != null && arySource.Length > 0);
int nCount = 0;
int nLast=0;
foreach(var n in arySource)
{
if (nCount == 0)
{
nLast = n;
nCount = 1;
}
else if(nLast == n)
{
++nCount;
}
else
{
--nCount;
}
}
if (CheckIfMoreThanHalf(arySource, nLast))
return nLast;
return -1;
}