剑指offer面试题3——数组中的重复数字

题目一:找出数组中重复的数字

       在一个长度为n的数组里的所有数字都在0~n-1的范围内。数组中某些数字是重复的,但不知道有哪几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如,输入长度为7的数组{2,3,1,0,2,5,3},那么对应输出是重复的数字2或者3。

思路:

       常规的办法可以将数组进行排序,复杂度为O(nlogn),然后遍历排序之后的数组,输出重复的数字。还有一种思路就是利用hash表,用空间换时间,遍历数组中的元素,在hash表对应的地方进行标记,最后遍历hash表就可以找到重复的元素。这里提供一种方法,可以注意到n个元素如果互不相同,那么排好序的结果就是元素的值等于元素下标的值。比如长度为7的数组不含有相同的元素,排序结果为{0,1,2,3,4,5,6}。就是从第一个元素开始遍历数组,当数组元素的值与元素的下标值不同时,比如a[2]=3,那么将a[2]和a[3]进行比较,如果a[3]也等于3,那么就找出了一个重复的数字。如果a[3]=4,那么就将a[2]和a[3]的值交换一下,a[3]=3,a[2]=4,再将a[2]和a[4]进行比较,如此循环往复,依次遍历数组中的元素就可找出其中重复的元素。

 

代码:

bool duplicate(int numbers[], int length, int* duplication)
{
	//考虑数组为空数组的特殊情况
	if (numbers == nullptr || length <= 0)//这里要十分注意,nullptr代表空指针,而null在编译的时候会被当做数组0,nullptr则不会
	{
		return false;
	}
	//考虑输入的数组元素不符合规定的范围的情况
	for (int i = 0; i < length; i++)
	{
		if (numbers[i]<0 || numbers[i]>length - 1)
			return false;
	}
	for (int i = 0; i < length - 1; i++)//和++i没区别
	{
		while (numbers[i]!=i)
		{
			if (numbers[i] == numbers[numbers[i]])
			{
				*duplication = numbers[i];
				return true;
			}
			//交换numbers[i]和numbers[numbers[i]]
			int temp = numbers[i];
			numbers[i] = numbers[temp];
			numbers[temp] = temp;
		}
	}
	return false;
}

题目二:不修改数组找出重复的数字

        在一个长度为n+1的数组里的所有数字都在1~n的范围内。所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但是不能修改输入的数组。例如,如果输入长度为8的数组{2,3,5,4,3,2,6,7},那么对应的输出是重复的数字2或者3。

思路:

      这道题常规思路依旧可以采用以空间换时间的hash方法。这里还有另外一种思路,就是把1~n的数字分为两部分,1~m和m+1~n。如果1~m的数字的数目超过m,那么这一半的区间里一定包括重复的数字:否则另一半m+1~n的区间里一定包含重复的数字,我们可以继续把重复的区间一分为二,直到找到一个重复的数字。但是这个代码有一个缺点,就是不能找全重复的数字,比如1~2的区间内,数字总数为2,但是可能是0个1和2个1这种情况。如果输入长度为n的数组,那么函数countRange将被调用O(logn)次,每次需要O(n)的时间,总的复杂度为O(nlogn),空间复杂度为O(1),相当于用时间换取了空间。所以做题的时候要向面试官问清楚,能不能改动原始数据,以及在时间复杂度和空间复杂度上更侧重于哪一个。

代码:

int getDuplication(const int*numbers, int length)
{
	if (numbers == nullptr)
		return -1;
	int start = 1;
	int end=length - 1;
	while (end>=start)
	{
		int middle = ((end - start) >> 1) + start;
		int count = countRange(numbers, length, start, middle);
		if (end == start)
		{
			if (count > 1)
				return start;
			else
				break;
		}
		if (count > (middle - start + 1))
			end = middle;
		else
			start = middle + 1;

	}
	return -1;
}


int countRange(const int*numbers, int length, int start, int end)
{
	if (numbers == nullptr)
		return 0;
	int count = 0;
	for (int i = 0; i < length; i++)
	{
		if (numbers[i] >= start && numbers[i] <= end)
			count++;
		return count;
	}
}

 

复习:

第一题的思路还是比较好想,第二题的思路有点巧妙,分组考虑的思想要好好领悟一下。还有函数的参数需要什么,不需要什么要考虑清楚。

二刷代码:

//第一题
bool duplicate(int numbers[], int length, int *duplication)
{
	//考虑输入的数组为空,数组长度小于1
	if (numbers == nullptr || length <= 0)
		return false;

	//考虑数组元素的值要在0到length-1的范围内
	for (int i = 0; i < length; i++)
	{
		if (numbers[i]<0 || numbers[i]>length - 1)
			return false;
	}

	for (int i = 0; i < length; i++)
	{
		while (numbers[i]!=i)
		{
			if (numbers[i] = numbers[numbers[i]])
			{
				*duplication = numbers[i];
				return true;
			}
			//交换numbers[i]和numbers[numbers[i]]
			int temp = numbers[i];
			numbers[i] = numbers[numbers[i]];
			numbers[numbers[i]] = temp;

		}
	}
	return false;
	
}

//第二题
int getDuplication(const int*numbers, int length)
{
	if (numbers == nullptr || length <= 0)
		return -1;


	int start = 1, end = length - 1;

	while (end>=start)
	{
		int middle = start + (end + start) >> 1;
		int count = countRange(numbers, length, start, middle);
		if (end == start)
		{
			if (count > 1)
				return start;
			else
				break;
			
		}

		if (count > (middle - start + 1))
		{
			end = middle;
		}
		else
		{
			start = middle + 1;
		}
	}
	return -1;
}




int countRange(const int* numbers, int length, int start, int end)
{
	if (numbers == nullptr || length <= 0)
		return -1;

	int count = 0;
	for (int i = 0; i < length ; i++)
	{
		if (numbers[i] >= start && numbers[i] <= end)
			++count;
	}

	return count;
}

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值