寻找数组中的重复数字

首先给出题目:在一个长度为n的数组里的所有数字都在0~n-1之间。数组中的数字是重复的,但具体不知道几个数字重复了,也不知道每个数字重复几次。请找出数组中的任意一个重复数字。
这道题看似简单,但实际上手仍然需要考虑很多种因素。一般的做法便是先将数组进行排序,然后比较前一个数字与后一个数字是否相等。如果相等就返回他。如果不相等就向后移动,知道数组结束。这种方法确实简单,而且很容易想明白。但排列一个长度为n的数据需要的时间为O(nlogn)的时间,很明显这种方法时间复杂度过高。

代码如下:

void Sort(int *arr,int len)
{
	int tmp =0;
	for( int i = 0;i<len;i++)
	{
		for(int j=0;j<len-1;j++)
		{
			if( arr[j] >arr[j+1] )
			{
				tmp = arr [j];
				arr[j] = arr[j+1];
				arr[j+1] = tmp;
			}
		}
	}
}
void SearchData(int *arr,int len)
{
	Sort(arr,len);
	int i = 0;
	while(i < len)
	{
		if( arr[i] == arr[i+1] )
		{
			printf("%d ",arr[i]);
		}
		i++;
	}
}

那我们换一种思路,首先数组的下标是从0开始,数据也是从0开始到n-1结束。那么我们可以先遍历整个数组,到扫描到下标为i的数字(m)时,将他与下标i做比较,如果不相等,就将他与第m个数字作比较如果相等,就找到了重复数字,如果不相等,则与第m个数字交换位置,直到找到重复数字。这种方法的好处就是活用了数组的下标与数组元素之间的关系,节约了时间,时间复杂度为O(n)。
现给出图解:
举个例子{3,2,4,0,3,1,5,7,8}。
在这里插入图片描述

3不等于0号下标,将3与3号下标数字0比较,发现不等相等,将0号下标与3号下标数字交换
在这里插入图片描述
0号下标与数字0相等,接下来重复以上操作,数字2与下标1不相等,将其与下标2数字4比较发现仍不相等,交换。
在这里插入图片描述
数字4与1号下标比较不相等,与4号下标数字3比较不相等,交换。
在这里插入图片描述
数字3与下标1不相等,与3号下标数字3比较发现,两者相等。此时便找到了重复元素3。

代码如下:

bool Searchdata(int *arr,int len,int *ddata)
{
	if(arr==nullptr||len<=0)
	{
		return false;
	}
	for(int i=0;i<len;i++)
	{
		if(arr[i]<0||arr[i]>len-1)
		{
			return false;
		}
	}
	for(int i=0;i<len;i++)
	{
		while(arr[i]!=i)
		{
			if(arr[i]==arr[arr[i]])
			{
				*ddata=arr[i];
				return true;
			}
			int tmp=arr[i];
			arr[i]=arr[tmp];
			arr[tmp]=tmp;
		}
	}
	return false;
}

接下来我们讨论一下影响因素,例如不能修改数组中的数字,从而找出重复数字。
对于这种情况的话,既然不能修改数组中的数字,那么我们可以利用一下辅助空间。首先创建一个大小为n+1的空间,将数组元素全部初始化为-1,作为标记。将原本的数组拷贝到辅助数组中,但这里注意的是将原本数组元素m拷贝到辅助数组下标m的位置。这样只需要一个判断语句(如果辅助数组m位置的元素与数组元素m不相等,就拷贝。若相等,就可以找到重复数字。
代码如下:

void Seachdata(int const *arr,int len,int *val)
{
	if(arr==nullptr||len<=0)
	{
		return;
	}
	int *brr =new int[len+1]();
	int i=0;
	for(;i<len+1;i++)
	{
		brr[i]=-1;
	}
	i=0;
	for(;i<len;i++)
	{
		if(brr[arr[i]]!=arr[i])
		{
			brr[arr[i]]=arr[i];
		}
		else
		{
			*val=arr[i];
			break;
		}
	}
	delete[]brr;
}

那要是不使用辅助空间呢?有没有好的办法可以解决呢?
当然有了,这时候我们可以思考一下既然是找到一个数字,而且是在数组中重复的。对于查找一个数字,最经典莫过于二分法查找(前提有序的)。我们这里可以引用一下二分法的思想,首先找出数组的中点m。他便将数组分成1 ~ m 和 m+1 ~ n-1两个区间。如果1 ~ m这个区间的数在整个数组中出现次数大于m那么重复数字便出现在1 ~ m这个区间,反之在m+1 ~n-1区间。然后将重复区域继续划分,直到找到重复元素。
例如:{3,2,4,0,3,1,5,7,8}。
数组的取值范围是 0 ~ 8, 中间元素3将其分为0 ~ 3与4 ~ 8 两个区间,然后我们统计0 ~ 3这4个数字在整个数组中出现的次数为 5,所以0-3这四个数字一定有一个数字重复。继续划分成0~ 1与2 ~ 3。2 ~ 3 在整个数组中出现了3次,所以 2 ~ 3 为重复区域。之后统计2与3 在数组中出现的次数,发现 3 出现两次所以 3便是重复数字。
这种方法,时间复杂度为O(nlogn),空间复杂度为O(1)。典型的以时间换时间。但这种方法不能找到所有的重复元素。
代码如下:

#include<iostream>
int Getcount(int const *arr,int len,int hhead,int ttail)
{
	int count = 0;
	int i=0;
	for(;i<len;i++)
	{
		if(arr[i] >= hhead && arr[i] <= ttail)
		{
			++count;
		}
	}
	return count;

}

int Searchdata(int const*arr, int len)
{
	int head = 1;
	int tail = len-1; 
	
	while(tail >= head)
	{
		int mid=(head+tail)/2;
		int count=Getcount(arr,len,head,mid);
		if(head == tail)
		{
			if(count>1)
			{
				return head;
			}
			else
			{
				break;
			}
		}
		if( count>(mid-head+1) )
		{
			tail=mid;
		}
		else
		{
			head=mid+1;
		}
	}
	return -1;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值