LeetCode287 寻找重复数(Floyd判圈算法)

原题目

给定一个包含 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算法

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Baal Austin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值