Find the Duplicate Number寻找重复数

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 之间,包括 1 和 n ,可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

示例 1:

输入: [1,3,4,2,2]
输出: 2

示例 2:

输入: [3,1,3,4,2]
输出: 3

说明:

  1. 不能更改原数组(假设数组是只读的)。
  2. 只能使用额外的 O(1) 的空间。
  3. 时间复杂度小于 O(n2) 。
  4. 数组中只有一个重复的数字,但它可能不止重复出现一次。
这道题如果没有说明中的限制条件,允许申请O(n)的辅助空间的话,可以用数组代替哈希表来做,但是题目只能使用O(1)的辅助空间,这道题才变得有意思起来。基于暴力法和数组哈希表法这里不再赘述,提供两种新的解题思路, 二分法循环链表法

二分法:

很多博客直接上来就是代码,让人云里雾里,解释的不也到位,需要阅读者花费很长时间来理解和吸收。这里直接以举例的方式来讲解。比如原数组a为(重复数字为2)

[1,2,2,3,4,5]

一共n+1个数字(n=5),数字范围为1-n及1-5,根据抽屉原理,在数字1-5中有6个数字,则至少有一个数字出现了两次以上。我们初始化low=1(数字的范围下限),high=5(数字的范围上限),mid=(high+low)/2=3,则mid把原数组分为1-3(包含3)和大于3的部分,如果原数组的数字在1-3的出现的次数cnt大于mid(简单理解为数字1-3最多只能包含3个数字,如果包含的数字个数大于3个,那么一定会出现重复的数字,及重复的数字一定在1-3的范围内),所以每次二分,在每个二分循环内都遍历一次数组,时间复杂度为O(nlogn),空间复杂度为O(1)。

代码如下:

int findDuplicate(vector<int>& nums) {
	//数字下限
	int low = 1;
	//数字上限
	int high = nums.size() - 1;
	while (low < high) {
		int cnt = 0;
		int mid = (low + high) / 2;
		for (int i = 0; i < nums.size(); i++) {
			//统计小于等于mid的个数
			if (nums[i] <= mid) {
				cnt++;
			}
		}
		//如果大于mid则在数字在小的那部分,及low-mid
		if (cnt > mid) {
			high = mid;
		}
		//否则,在大的那部分mid+1-high
		else {
			low = mid + 1;
		}
	}
	return low;
}

循环链表法:

这种方法非常巧妙,中心思想是如果数组中存在重复数字,那对数组a而言:

i->a[i]->a[a[i]]->a[a[a[i]]]

的访问方式最后一定会陷入循环,且循环的数就是重复的数,以下举例说明:

对于不重复的数组:

[1,2,6,3,4,5,8]

我们以下方式生成链表:下标(0)->下标对应的值(1)->下标对应的值[下标对应的值](2)。。。的方式来遍历数组,可以得到如下链表:

0->1->2->6->8->nullptr

不会出现循环,如果对于重复数组:

[1,2,2,3,4,5]

构建的链表为:

0->1->2->2->2->2->......(为什么不是第一个2,因为循环是从第二个2才开始循环,第一个2只是刚好下标为1的数为2)

节点2开始循环,重复的数字也是2,所以思路转化为寻找带环链表的第一个环节点,通过快慢指针来做,之前leetcode已经分析过,点击这里,这里不再赘述。

int findDuplicate(vector<int>& nums) {
	int n = nums.size();
	if (n > 0) {
		//访问下标0的值
		int slow = nums[0];
		//“下标0的值”作为下标访问
		int fast = nums[nums[0]];
		while (slow != fast) {
			slow = nums[slow];
			fast = nums[nums[fast]];
		}
		fast = 0;
		while (slow != fast) {
			slow = nums[slow];
			fast = nums[fast];
		}
		return slow;
	}
	return -1;
}





猜你喜欢

转载自blog.csdn.net/qq_26410101/article/details/80450531