LeetCode - Find the Duplicate Number
题目
Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive), prove that at least one duplicate number must exist. Assume that there is only one duplicate number, find the duplicate one.
Note:
- You must not modify the array (assume the array is read only).
- You must use only constant, O(1) extra space.
- Your runtime complexity should be less than O(n2).
- There is only one duplicate number in the array, but it could be repeated more than once.
分析
这个题是两部分问题,第一部分的证明看起来比较直接,在数学上可以通过反证法证明。
第二部分是说现在有 n+1 个位置,放进去的数字都是从 1 到 n 的,但是存在一个重复的数字,其他的都不重复,让用 O(1) 的空间复杂度和少于O(n2)的时间复杂度找出这个重复的数字。示例输入输出可能是
[2,3,1,2] -> 2
[2,2,1,2,2] -> 2
一个比较详细的解答可以参考
https://segmentfault.com/a/1190000003817671
这里我想解释一下链接中的最后一个方法:映射找环法。代码可以参考上述链接,这里只是将其原理进行一个详细的论述
可以先看一下上述链接中的解释,这个算法的思路如下:
将数组中每个元素都看成链表中的一个节点,链表节点有两个基本要素就是节点的值(val)和指向下一个节点的指针(next),我们假设这个数组表示成 array[], 某个节点的索引是 index,在这个算法所想象的链表中:
- 节点的值 val 就是数组在这个位置的值 array[index]
- 节点的指向想一个节点的指针 next 也是数组在这个位置的值 array[index]
举个例子,数组 [2,3,1,2] 构成的链表如下图所示
我们发现这个图中出现了一个环,下面我们说明在给定的条件下,从数组的第0个元素出发,一定会进入一个环中,且这个环包含了重复的那个数字:
- 假设从第0个元素出发,最后没进入环中(反证),这就说明由0出发的链表的尾节点的next是null
- 然而给定条件下,数组中每个元素的值都是1 - n之间的数字(同时根据之前的构造方法数组的值也就是其next指针),说明这个链表中不会出现指向null的节点
上面的1和2相互矛盾,说明从数组的第0个元素出发,一定会进入一个环中。
我们知道,指向环的入口元素(上例中的array[2]=1)的指针值(next)相同,而根据构next又等于节点的val,说明换入口元素的上一个节点就是重复的那个值。
事实上构造出来的图中可能存在多个环,但由于题的条件限定了只可能有一个重复元素,所以其中只有一个环会带着一个或多个“尾巴”,这个“尾巴”是进入环之前的部分。这个题的巧妙之处就在于数组中元素都在1-n之间,说明不会有节点的next值等于0,即array[0]一定是这样的一个“尾巴”的头部,而不会出现在环的内部。
这样原问题就转化为了在单链表中找环入口的问题(重复的数字是环入口前一个节点),利用快慢指针的办法可以很容易的写出代码(网上有很多资料,也可以看上文链接中的代码)
总结一下上面的几个关键点:
- 将数组看成一个链表,链表的 val 和 next 都是数组元素值
- 从第0个元素出发,必然会形成环
- 重复的数字节点在环的入口的前一个节点处
最后我想说的是,用数组表示链表或树状也是一种常见的办法,效率很高,以前学数据结构的时候学到过但后来一直没用过,可以看看 java 的 TImer 类的源代码中最小堆是如何实现的。