剑指offer:数组中重复的数字

剑指offer:数组中重复的数字

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

问题描述

在一个长度为n的数组里的所有数字都在0~n-1的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

输入输出
输入:[2,3,1,0,2,5,3]
输出:2或3

算法描述

方法一:预排序 + 扫描一遍

先把输入的数组排序。从排序的数值中找出重复的数字,只需要和相邻的数字进行比较是否相等,扫描一遍即可。

算法分析
  • 时间复杂度:O(nlgn)
  • 空间复杂度:O(1)
方法二:哈希表

从头到尾扫描数组的每个数字,每扫描到一个数字,判断哈希表里是否已经包含了这个数字。如果哈希表里没有包含这个数字,就将它加入哈希表中。

算法分析
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
方法三:原地重排

注意到数组中的数字都在0~n-1的范围内。如果这个数组没有重复的数字,那么数字i就会对于下标i的位置;如果有重复数字,必然发生冲突。可以利用数字范围大小的特点,直接在原地重排。

思路:从头到尾依次扫描这个数组中的每个数字。当扫描到下标为i的数字时,首先判断数字是否等于i:

  • 如果相等,继续下一个下标位置;
  • 如果不相等,设numbers[i]=value,需和numbers[value]进行比较
    • 如果相等,则说明之前已经有值为i的数字出现,所以有重复
    • 如果不想等,交换这两个数numbers[value]和numbers[i]。(注意交换后,下标为i的位置仍然要继续判断)
算法分析
  • 时间复杂度O(n)

  • 空间复杂度O(1)

具体代码如下:
bool duplicate(int numbers[],int length,int* duplication){
	if(numbers == nullptr || length < 0){
		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;i++){
		while(numbers[i]!=i){
			if(numbers[i]!=numbers[numbers[i]]){
				// swap(numbers[i],numbers[numbers[i]])
				int t = numbers[i];
				numbers[i] = numbers[numbers[i]];
				numbers[numbers[i]] = t;
			}
			else 
				*duplication = numbers[i];
			return true;
		}
	}
	return false;
}

题目二:不修改数组找出数组中任意一个重复的数字

问题描述

在一个长度为n+1的数字中,所有的数字都在1~n的范围内,所以数组至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。

算法描述

方法一:

利用题目一的方法二 或 创建一个辅助数组,利用题目一的方法三修改辅助数组

算法分析
  • 时间复杂度O(n)

  • 空间复杂度O(n)

方法二:

我们把从1~ n 的数字从中间的数字middle分为两部分,前面一半为1 ~ middle,后面一半为middle + 1 ~ n。统计[1,middle]这个区间的整数在整个数组中出现的次数

  • 如果次数超过m,那么该区间里一定包含了重复数字,则检验的数组目标就可以从[start,end]转变为[start,middle],即end = middle
  • 否则,后一半的区间里一定包含了重复数字,则检验的数组目标就可以从[start,end]转变为[middle+1,end],即start = middle + 1

缩小了待检验区间的长度后,继续分两部分,直到找到一个重复的数字。整个过程类似二分查找。

算法分析
  • 时间复杂度O(nlgn)

  • 空间复杂度O(1)

具体代码
int getDuplication(const int* numbers,int length){
	if(numbers == nullptr || length <= 0)
		return -1;
	int start = 1;
	int end = length-1;
	while(end >= start){
		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;		//该区间start ~ middle中,有重复的数字,所以可以缩小区间范围从[start,end]变成[start,middle]
		else
			start = middle +1;	//该区间start ~ middle中,没有重复的数字,则看后半区间
		
	}
	return -1;
}

int countRange(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;

}

参考资料:剑指offer 2.3.1 数组 面试题3:数组中重复的数字

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值