打卡!!!每日一题
今天给大家带来一道很有意思的题目,带你认识一下不一样的链表。
题目描述:287. 寻找重复数
给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。
你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。
题目示例:
这道题我拿过来的第一个思路就是使用位运算,当然位运算也完全没问题,因为一旦涉及到限制空间复杂度的时候,建议大家也要往位运算的方向去思考。
然后我又想到JDK 中的 Arrays.sort(int[] a) 是用双枢轴快速排序算法实现的,该算法具有O(nlogn) 平均复杂度并且就地执行(例如不需要额外的空间),所以我们可以先排序在相邻元素进行比较,因为思路过于简单,就不在过多的讨论,代码如下:
public int findDuplicate(int[] nums) {
int length = nums.length;
if (length <= 1) return nums[0];
Arrays.sort(nums);
for (int i = 0; i < length - 1; i++) {
if (nums[i] == nums[i + 1]) {
return nums[i];
}
}
return nums[0];
}
不过本文的重点既不是位运算,也不是排序,而是循环链表的思路,可能有小伙伴读到这儿感觉一脸懵了,这就是个数组怎么会和链表扯上关系,别着急,听我慢慢给你道来。
我给大家举两个例子:
1.数组中没有重复的数,以数组 [1,3,4,2]为例,们将数组下标 n 和数 nums[n] 建立一个映射关系 f(n),其映射关系 n->f(n)为:
0->1
1->3
2->4
3->2
所构成的链表如下所示:
0->1->3->2->4->null
可以看出上面所构成的链表是单向链表,所以不存在重复元素。
2.数组中有重复的数,以数组 [1,3,4,2,2] 为例,我们将数组下标 n 和数 nums[n] 建立一个映射关系 f(n),其映射关系 n->f(n)为:
0->1
1->3
2->4
3->2
4->2
所构成的链表如下所示:
0->1->3->2->4->2->4->2->……
这里 2->4 是一个循环,那么这个链表可以抽象为下图:
很明显这个链表是有环存在的,这是因为重复元素所导致的,所以解决本题的关键就是找到这个链表的环入口
找到环入口最经典的解决办法就是:快慢指针
- 慢指针走一步 slow = slow.next ==> 本题 slow = nums[slow]
- 快指针走两步 fast = fast.next.next ==> 本题 fast = nums[nums[fast]]
相信上面的这两个快慢指针的定义应该不难理解,因为我们的链表就是由下标–>元素值
初始值:slow,fast均指向0号位
第一次:slow->1,fast->3
第二次:slow->3,fast->4
第三次:slow->2,fast->4
第四次:slow->4,fast->4
slow和fast会在环中相遇,先假设一些量:
- 环长为 KL
- 从起点到环的入口的步数是 a
- 从环的入口继续走 b 步到达相遇位置
- 从相遇位置继续走 c 步回到环的入口
则有 b + c = L b+c=L b+c=L 其中 L、a、b、c 都是正整数
根据上述定义,慢指针走了 a+b 步,快指针走了 2(a+b)步。
从另一个角度考虑,在相遇位置,快指针比慢指针多走了若干圈,因此快指针走的步数还可以表示成 a + b + k L a+b+kL a+b+kL,其中 k 表示快指针在环上走的圈数。联立等式,可以得到
2 ( a + b ) = a + b + k L 2(a+b)=a+b+kL 2(a+b)=a+b+kL
解得 a = k L − b a=kL−b a=kL−b ,整理可得
a = ( k − 1 ) L + ( L − b ) = ( k − 1 ) L + c a=(k−1)L+(L−b)=(k−1)L+c a=(k−1)L+(L−b)=(k−1)L+c
所以我们在重新一个指针finder指向起始位置,让慢指针从相遇位置一步一步的往前走,刚好会在环的入口处相遇,因为在finder指针刚好走a步的时候,慢指针走了(k-1)L+c步,而(k-1)L相当于绕环走了若干圈又回到和快指针相遇的位置,而c步又刚好回到环的入口。
public int findDuplicate(int[] nums) {
int slow = 0;
int fast = 0;
slow = nums[slow];
fast = nums[nums[fast]];
while (slow != fast) {
slow = nums[slow];
fast = nums[nums[fast]];
}
int finder = 0;//指向起始位置
while (finder != slow) {
finder = nums[finder];
slow = nums[slow];
}
return finder;
}