Leetcode题解:287. Find the Duplicate Number寻找重复数

代码实现

只想抄个答案把题过了的可以直接复制下面的代码:

class Solution {
public:
	int findDuplicate(vector<int>& nums) {
		int slow = 0, fast = 0, tar = 0;

		do {
			slow = nums[slow];
			fast = nums[nums[fast]];
		} while (slow != fast);

		while (tar != slow) {
			slow = nums[slow];
			tar = nums[tar];
		}
		return tar;
	}
};

题目描述

描述

算法分析

由于题中的限制,所以不能用先排序再遍历的方法(不能更改原数组),也不能用无序集合或者散列表来做(只能使用常量空间),所以只能从数组本身找规律,这里介绍一种线性时间复杂度的算法。
首先我们有如下的一个数组,i为下标,num为数字,有nums[i] = num(nums为给定的数组):
数组
然后我们建立这样的映射F,使得F(i) = num
同时,如果给出了F(a) = b,我们再对b进行映射,会有F(b) = c,我们再对c做同样的操作,一直不断循环,反映到题中的数组上,就是如下的情况:
映射
这里我们起始给定a = 0,F(a) = 3,然后F(3) = 2,F(2) = 1,…,F(4) = 2,F(2) = 1,出现了循环,但是并不是因为有重复数字才出现环,我们先看重复数字会发生什么样的情况:

会出现多对一映射,即至少存在这样的a,b,c(a ≠ b),有F(a) = c,F(b) = c,反应到图中即为指向数字‘2’的有两个箭头(F(3) = 2,F(4) = 2)

很容易知道,如果我们对一个不存在重复数字的数组进行这样的循环映射操作,最后也会发生循环,且循环开始的下标值一定是第一个进行映射操作的下标值,即,如果我们从F(a) = b开始进行映射操作,最后仍会以F(a) = b的出现标志着进入循环(可以自己画一个简单的图来验证)。
但是如果有重复的数字,我们就会发现,因为存在F(a) = c,F(b) = c,那么中间步骤一旦出现了F(x) = b,就会有这样的循环:F(c) = y, ..., F(x) = b, F(b) = c, F(c) = y,这种循环和刚刚讲的循环不同的地方在于,映射循环开始的下标值正好是发生重复的数字,所以问题就转化为如何找到这样的循环,以及循环开始的节点

代码分析

首先,接下来的操作仅作说明,如果想了解原理可以参考链表找环的起点
算法一共就两个要点,第一,找到环第二,找到环起点
先来看找环操作,如下:

do {
	slow = nums[slow];
	fast = nums[nums[fast]];
} while (slow != fast);

通过一快一慢两个指针进行循环映射操作(慢指针一次走一格,快指针一次走两格),两个指针相遇时,就说明指针已经进入环内(前提是必须有环)。
再看找环起点的操作,如下:

while (tar != slow) {
	slow = nums[slow];
	tar = nums[tar];
}
return tar;

slow是刚才循环结束后的慢指针,tar初始值是0,两个指针同时向前走,相遇的时候tar指针就是环的起点,即数组重复的数字。
整个流程如下:

int findDuplicate(vector<int>& nums) {
	int slow = 0, fast = 0, tar = 0;
	
	do {
		slow = nums[slow];
		fast = nums[nums[fast]];
	} while (slow != fast);
	
	while (tar != slow) {
		slow = nums[slow];
		tar = nums[tar];
	}
	return tar;
}

最后,这个代码虽然在leetcode上可以通过,但是还是有一个bug的,假如输入{2,1,0,3,3}这样的数组你会发现输出结果是0,因为0,1,2这3个数组成了环,相当于我们一开始说的无重复数字的数组产生的环,这时候就需要额外的进行判断,有兴趣的可以进行尝试改进(提示:观察这样的数组输出结果的规律,以及其中重复数字出现位置的规律

猜你喜欢

转载自blog.csdn.net/qq_37435078/article/details/86680096