【问题标题】:Understanding why Floyd's tortoise and hare algorithm works when applied to an array of integers理解为什么弗洛伊德的龟兔赛跑算法在应用于整数数组时有效
【发布时间】:2021-02-10 03:25:43
【问题描述】:

我试图解决这个 leetcode 问题https://leetcode.com/problems/find-the-duplicate-number/,使用我自己的龟兔算法实现,当给定以下整数数组时,会导致无限循环:

[3,1,3,4,2]

只有在跟踪我的算法后,我才能看到慢跑者和快跑者永远不会同时接受两个重复值。这是我的伪代码算法:

initialize fast and slow runners to 0

while(true)

   move fast runner two indices forward
   move slow runner one index forward

   if arr[fast] == arr[slow] and fast != slow
      return arr[fast] // this is the duplicate

现在,我确信精通离散数学的人能够直观地知道,如果不首先像我必须做的那样追溯一个示例,这种方法不会导致正确的解决方案。

我可以做出哪些推论或观察来让我看到这个算法不起作用?我想知道如何通过一系列逻辑语句直观地识别此逻辑中的缺陷。换句话说,为什么这两个跑步者永远不会在这个例子中找到重复项的解释是什么?我觉得这可能与计数有关,但我在离散方面没有很强的背景。

为了澄清,我已经查看了正确的实现,所以我知道解决它的正确方法是什么。我只是认为这种方式与将其应用于链表太相似了,在链表中,您可以将快跑者上移两个节点,将慢跑者上移一个节点。感谢您的帮助。

【问题讨论】:

  • 您似乎希望该算法能够探索数组索引的每个组合。兔子有 5 个索引,乌龟有 5 个索引,所以总共有 25 种可能的组合。问题是兔子在 5 次移动后就抓住了乌龟,所以他们将探索 25 种可能组合中的 5 种,特别是 {1,2}、{2,4}、{3,1}、{4,3} , {0,0}。这些都不是您要寻找的组合:{0,2}。为什么兔子跑了5步就抓到了乌龟?乌龟移动了5个位置,兔子移动了10个位置,都回到了起点。
  • 很好的解释。我想我只是没有意识到只有 5 种组合会被探索。

标签: algorithm logic discrete-mathematics


【解决方案1】:

当您在链表中检测循环时,弗洛伊德的乌龟算法会起作用。它依赖于这样一个事实,即如果两个指针以不同的速度移动,它们之间的差距将不断增加到一个极限,之后如果存在循环,它将被重置。
在这种情况下,算法确实找到了一个循环,因为两个指针在一些迭代后都收敛到索引 0。但是,您不想在这里检测循环;您正在尝试查找重复项。这就是它陷入无限递归的原因:它旨在检测一个循环(它正确地做到了),但在其基本实现中不检测重复。

为了澄清,这里是在您的示例数组上创建的示例链表。

3 -> 1 -> 3 -> 4 -> 2
'--<----<----<----<-'

如果您运行 Floyd 算法,您会发现循环将在索引 0 处被检测到,因为两个指针都会在那里收敛。它通过检查fastslow 是否指向相同位置 而不是它们是否具有相同的节点值(fast==slowfast.value==slow.value 不同)来工作。

您正在尝试通过比较节点上的值来检查重复项,并检查节点是否指向同一位置。这实际上是一个缺陷,因为 Floyd 的算法会检查两个指针​​是否指向同一位置以检测循环。
您可以阅读this simple, informative proof 以提高您对指针为何会收敛的直觉。

【讨论】:

  • 我有点遵循。但是为什么不能把数组中的每个位置都当成链表中的一个节点呢?
  • 感谢您提供的详细信息。我的想法是,如果循环运行的时间足够长,最终一个指针将位于索引 0 处的 3 处,而另一个指针将位于索引 2 处的 3 处。但是这永远不会发生,这会导致无限循环。如果一个指针比另一个移动得快,那不应该最终发生吗?
  • 看两个指针之间的差距。它从 0 开始,然后是 1,然后是 2,以此类推。然而,当它达到 5 时,它将被重置为零,因为列表长度为 5 并且指针会聚。这就是循环的重点——它在某些条件下重复/循环,但不保证所有可能的配对都会被检查值而不是位置。
  • 有趣啊!这就是我无法解决的问题。当它们之间的间隙等于数组的长度时,指针重置并收敛的实现。出于某种原因,这对我来说并不明显。
【解决方案2】:

这不是一个坏主意。这是 Python 中的一个实现:

class Solution:
    def findDuplicate(self, nums):
        slow, fast = 0, 0
        while True:
            slow = nums[nums[slow]]
            fast = nums[fast]
            if slow == fast:
                break

        fast = 0
        while True:
            slow, fast = nums[slow], nums[fast]
            if slow == fast:
                break
        return slow

我们也可以使用二分搜索:

class Solution:
    def findDuplicate(self, nums):
        lo, hi = 0, len(nums) - 1
        mid = lo + (hi - lo) // 2
        while hi - lo > 1:
            count = 0
            for num in nums:
                if mid < num <= hi:
                    count += 1
            if count > hi - mid:
                lo = mid
            else:
                hi = mid
            mid = lo + (hi - lo) // 2
        return hi

在 C++ 中:

// The following block might slightly improve the execution time;
// Can be removed;
static const auto __optimize__ = []() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    return 0;
}();


// Most of headers are already included;
// Can be removed;
#include <iostream>
#include <cstdint>
#include <vector>


static const struct Solution {
    static const int findDuplicate(
        const std::vector<int>& nums
    ) {
        int slow = 0;
        int fast = 0;

        while (true) {
            slow = nums[nums[slow]];
            fast = nums[fast];

            if (slow == fast) {
                break;
            }
        }

        fast = 0;

        while (slow != fast) {
            slow = nums[slow];
            fast = nums[fast];
        }

        return slow;
    }
};

【讨论】:

  • 二进制搜索仅适用于已排序的输入。很明显,问题的输入没有排序。如果有的话,还有更简单的算法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-11-15
  • 1970-01-01
  • 2011-04-22
  • 2013-03-28
  • 2021-03-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多