弗洛伊德的乌龟和兔子

弗洛伊德的乌龟和兔子

在介绍这个概念之前,首先让我们看一道经典的面试题:

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
示例1:

输入: [1,3,4,2,2]
输出: 2

示例2:

输入: [3,1,3,4,2]
输出: 3

说明:
1.不能更改原数组(假设数组是只读的)。
2.只能使用额外的 O ( 1 ) O(1) O(1)的空间。
3.时间复杂度小于 O ( n 2 ) O(n^2) O(n2)
4.数组中只有一个重复的数字,但它可能不止重复出现一次。

(这道题(据说)花费了计算机科学界的传奇人物Don Knuth 24小时才解出来。并且我只见过一个人(注:Keith Amling)用更短时间解出此题。)

初次尝试:排序比较法

我们第一眼见到这道题,会首先想到先通过排序使得数组有序,然后再两两比较数组元素,这样就能轻易的得到重复的数字。可是,正准备开码,我们注意到题目说明数组仅为只读。因此我们无法排序,只能放弃这种方法。

再次尝试:哈希字典法

再思考,既然不能更改,那我们可以额外开销 O ( n ) O(n) O(n)个空间来制作hash表,通过一次hash来找到所需元素。但是,我们发现题目已经限制了空间。因此,我们必须再换一种符合题目说明的方法。

最终方案:快慢指针法

绞尽脑汁后,我们终于想到了使用快慢指针,也就是弗洛伊德的乌龟和兔子来寻找这个循环元素。

但是这道题中并没有任何指针,只有一个数组。因此,我们需要使用抽象思维,将这个数组抽象成链表。

如何将数组抽象成链表?

假设我们有一个数组[1,4,3,5,3,2],现在我们将其抽象成一条拥有六个节点的链表,显然,这是一条循环链表:

1 -> 4 -> 3 -> 5 -> 3 -> 2

现在我们只需进行如下操作:

int a = 0;
while(ture)
{
    a = nums[a];
}

a = 1;
a = 4;
a = 3;
a = 5;
a = 2;
a = 3;

就能够达到遍历整个链表的效果。而要实现快慢指针,我们只需改变对变量a的操作次数即可。

fast = nums[nums[rabbit]];
slow = nums[turtle1];

前提:给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n)

有了这样的思想,我们终于可以开始设计算法:让一只兔子和一只乌龟同时从起点出发,则兔子与乌龟相遇时,兔子所走的位移就刚好是乌龟的两倍。现在假设进入循环之前的长度为 L L L,进入循环之后快慢指针第一相遇时兔子比乌龟多跑了 N N N圈, 每一圈的长度为 C C C,此时兔子在环内离入环节点的距离为 c c c
此时乌龟走过的距离为: L + c L + c L+c
此时兔子走过的距离为: L + c + N ∗ C L + c + N * C L+c+NC
由位移关系: 2 ∗ ( L + c ) = L + c + N ∗ C 2 * (L + c) = L + c + N * C 2(L+c)=L+c+NC
整理后得到: ( N − 1 ) ∗ C + ( C − c ) = L (N - 1) * C + (C - c) = L (N1)C+(Cc)=L
由此可知, 若此时有第二只乌龟同时从起点出发,第一只乌龟继续移动,那么两者必然会在入环节点处相遇。
在这里插入图片描述

最终的代码实现如下:

class Solution
{
public:
    int findDuplicate(vector<int>& nums) 
    {
        int rabbit = 0, turtle1 = 0;
        while(true)
        {
            rabbit = nums[nums[rabbit]];
            turtle1 = nums[turtle1];
            if(rabbit == turtle1) 
            {
                int turtle2 = 0;
                while(nums[turtle1] != nums[turtle2]) 
                {
                    turtle1 = nums[turtle1];
                    turtle2 = nums[turtle2];
                }
                return nums[turtle1];
            }
        }
    }
};
执行用时内存消耗
8ms12.1MB

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( 1 ) O(1) O(1)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值