数组中出现次数超过一半的数字

只有刷不完的题,没有过不去的坎。

今天,再来看一道题:

题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1, 2, 3, 2, 2, 2, 5, 4, 2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。

看到这道题很多人就会想要是这个数组是排序的数组就好了。如果是排好序的数组,那么我们就能很容易统计出每个数字出现的次数。题目给出的数组没有说是排序的,因此我们需要先给它排序。排序的时间复杂度是O(nlogn)。但我们要找更快的算法。(《剑指offer》)

解法一和快排有关,怎么说呢,这个解法一用到了快排中实现的一个partition函数。在此先把partition代码贴上:

void swap(int *elem1, int *elem2)
{
	int tmp = *elem1;
	*elem1 = *elem2;
	*elem2 = tmp;
}
//快速排序中的parition函数
int partition(int *data, int len, int start, int end)
{
	int small = 0;
	int index = 0;
	if (NULL == data || len <= end - start || start < 0 || end >= len)
		return -1;
	
	index = (end - start) / 2 + start;
	swap(&data[index], &data[end]);
	
	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;
}
这个函数做的事情就是在数组中取中间的那个数作为主元,借此把数组分为两部分,左边的数字比主元小,右边的数字比主元大。

现在再来思考一下题目:数组中有一个数字出现的次数超过了数组长度的一半。如果把这个数组排序,那么排序后位于数组中间的数字一定就是那个出现次数超过数组长度一半的数字。也就是说,这个数字就是统计学上的中位数,即长度为n的数组中第n/2大的数字。我们有成熟的O(n)的算法得到数组中任意地k大的数字,这种算法是受快排启发。在快速排序算法中,我们先在数组中选一个数字,然后调整数组中数字的顺序,使得比选中的数字小的数字都排在它的左边,比选中的数字大的数字都排在它的右边。如果选中的数字的下标(调整后的)刚好是n/2,那么这个数字就是中位数。如果它的下标大于n/2,那么中位数应该位于它的左边,我们可以接着在左边部分的数组中查找。如果下标小于n/2,那么中位数应该位于它的右边,我们可以接着在它的右边部分的数组中查找。这是一个典型的递归过程。(《剑指offer》)

代码如下:

int more_than_half_num(int data[], int len)
{
	int i = 0; 
	int index = 0;
	int cnt = 0;
	int mid = 0;
	int start = 0;
	int end = len - 1;
	if (NULL == data || len <= 0)
		return -1;
	
	mid = len >> 1;
	index = partition(data, len, 0, len - 1);
	while (index != mid)
	{
		if (index > mid)
		{	
			end = index - 1;
			index = partition(data, len, start, end);
		}
		else
		{
			start = index + 1;
			index = partition(data, len, start, end);
		}
}

	for (i = 0; i < len; i++)
	{
		if (data[i] == data[index])
			cnt++;
	}
	if (cnt > mid)		//存在出现次数超过一半的数
		return index;
	else
		return -1;
}

但是这个程序有一个缺点就是它改变了原来数组的结构,通常一个查找算法是没必要改变原数组结构的。因此我们又找到了解法二:

数组中有一个数字出现的次数超过数组长度的一半,也就是说它出现的次数比其它所有数字出现的次数和还多,因此可以考虑在遍历数组的时候保存两个值:一个是数组中的一个数子,一个是次数。当我们遍历到下一个数字的时候,如果下一个数字和我们之前保存的数字相同,则次数+1,如果不同,则次数-1.如果次数为0,我们需要保存下一个数字,并把次数设为2.由于我们要找的数字出现的次数比其它所有数字出现的次数和还要多,那么要找的数字肯定是最后一次把次数设为1时对应的数字。(《剑指offer》)

下面是这种思路的参考代码:

int more_than_half_num_2(int data[], int len)
{
	int i = 0;
	int result = 0;
	int cnt = 1;
	if (data == NULL || len <= 0)
		return -1;


	for (i = 1; i < len; i++)
	{
		if (0 == cnt)
		{
			result = i;
			cnt = 1;
		}
		else if (data[i] == data[result])
			cnt++;
		else
			cnt--;
	}

	for (i = 0; i < len; i++)
	{
		if (data[i] == data[result])
			cnt++;
	}
	if (cnt > len / 2)
		return result;
	else
		return -1;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fireplusplus

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值