原题目
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
示例 1:
输入: [1,3,4,2,2]
输出: 2
示例 2:
输入: [3,1,3,4,2]
输出: 3
说明:
不能更改原数组(假设数组是只读的)。
只能使用额外的 O(1) 的空间。
时间复杂度小于 O(n2) 。
数组中只有一个重复的数字,但它可能不止重复出现一次。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-the-duplicate-number
题目大意
找出现次数出现两次以上的数字,且重复数字只有一个
且n+1个数的值只能为1~n之间
要求:
1.不能更改原数组,也就是说不能排序,前后两个相邻的比
2.只能使用额外的 O(1) 的空间,也就是说不能用哈希表法,或另开个数组排序
3时间复杂度小于n的平方,也就是说不能将每个值与其他值进行一一比较。
题目分析
Floyd判圈算法(龟兔赛跑算法):
介绍:在一个单向链表中,我们如何判断其中存不存在环,如果存在,那么环的长度是多少,环的起始位置在哪
(一)首先判断是否是环的方法:
1.哈希表法:
用哈希表保存已经走过的结点指针,查找判断当前指针是否在之前已经出现过。
2.龟兔算法(Floyd cycle detection):
龟兔其实是快慢指针,假设为 p、q,初始时都指向单链表表头,p 指针每次移动一个结点,q 指针每次移动两个结点。如果 p、q 再次相遇,则说明有环。如果 q 走到链表末尾还没与 p 相遇,则无环。
证明:
令:head=头节点; entry:环的入口处节点;
meeting=相遇时所在的节点. C=环的长度;
L1=head到entry的距离; L2=entry到meeting的距离.
step1=慢指针走的步数 step2=快指针走的步数
慢的每次走一步(速度为1),快的每次走两步(速度为2).同时出发,相遇时有:
step1 = L1+L2+n1 * C
step2 = L1+L2+n2 * C
step1 * 2=step2(因为他们次数相同,速度为两倍,则步数也为两倍)
L1+L2+n1 * C = (L1+L2+n2 * C)/2
化简得: L1 = (n2-2 * n1) * C-L2
其中L1 和C是定值,这个等式显然有解,所以会相遇.
(二)计算环长度
上述算法刚判断出存在环C时,显然p和q位于同一节点,设其为节点M(相遇点meating)。显然,仅需令p不动,而q不断推进,最终又会返回节点M,统计这一次t推进的步数,显然这就是环C的长度。
(三)计算环起点
为了求出环C的起点,只要令p仍位于节点M,而令q返回起点节点H(头结点head)。随后,同时让p和q往前推进,且保持二者的速度相同:p每前进1步,q前进1步。持续该过程直至p与q再一次相遇,设此次相遇时位于同一节点E(环起点entry),则节点E即为从节点H出发所到达的环C的第一个节点,即环C的一个起点。
证明:
链表起点为节点H,环起点为节点E,p和q相遇时位于同一节点M,H和P之间的距离为L1,P和M之间的距离为L2,环长为C,这里两点之间的距离是指从一点走多少步可以到点另外一点。
当p和q相遇时,
p走的步数,step1=L1+L2+n1 * C ,n1表示相遇时q走的圈数
q走的步数,2 * step1= L1+L2+n2 * C,n2表示相遇时p走的圈数
两者相减:step1 = (n2 - n1) * C = L1 + L2 + n1 * C,由此可知p走的步数是环C的倍数,即 L1 + L2 刚好是环长度C的倍数。
相遇位置为环起点位置开始走了L2步,所以还要走L1步才能回到环起点,而L1是链表起点到环起点的距离,所以从链表起点到环起点的距离与相遇点回到环起点的距离,两者同时出发一定能在环起点相遇
(四)算法复杂度
时间复杂度
注意到当指针t到达环C的一个起点节点P时(此时指针h显然在环C上),之后指针t最多仅可能走1圈。若设节点S到P距离为N,环C的长度为M,则时间复杂度为O(N+M),是线性时间的算法。
空间复杂度
仅需要创立指针t、指针h,保存环长n、环的一个起点P。空间复杂度为O(1),是常数空间的算法。
题目解析:
因为每个数字在1~n范围内,又只有一个重复数字,则可以将数字作为下一个数的地址下标,这样便一定形成一个环,便可以用Floyd判圈算法,环的起点便是重复数字。
完整代码
int findDuplicate(int* nums, int numsSize){
int slow=nums[0],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;
}
执行用时 :
16 ms, 在所有 C 提交中击败了50.55%的用户
内存消耗 :
8.3 MB, 在所有 C 提交中击败了5.63%的用户
总结
理解运用Floyd算法