【问题标题】:Finding loop in linked list complexity?在链表复杂性中寻找循环?
【发布时间】:2015-11-26 11:42:27
【问题描述】:

在链表中找到循环的正常情况是移动一个指针一次,移动另一个指针两次。如果它们相遇,则链表中有一个循环。

但是如果我移动第二个指针三四次会发生什么。它会降低复杂度吗?为什么我们只需要移动第二个指针两次。

boolean hasLoop(Node first) {
    Node slow, fast; 
    slow = fast = first; 
    while(true) {
        slow = slow.next; 
        if(fast.next != null)
            fast = fast.next.next; 
        else
            return false;  

        if(slow == null || fast == null) 
            return false;

        if(slow == fast) 
            return true;
    }
}

为什么我不能有 fast.next.next.next 或 fast.next.next.next.next.next,而不是 fast.next.next?

【问题讨论】:

  • 我没有听懂这个问题,你能用代码说明你的意思吗?您是否希望在单链表中找到可能的循环(所有节点都只有指向后续节点的指针)?有一些不同的策略,将其转换为双向链表是一个选项,因为保留反向链接将使查找循环变得基本......如果你从前面遍历它,并找到一个节点反向链接不是您刚刚来自的链接,那么您可以假设两个节点链接到同一事物。关于移动指针,正如我所说,请显示代码...
  • @RichardEriksson 我认为这是一个理解问题,而不是一个实现问题 - 那么为什么加快更快的指针是不切实际的呢?
  • 啊。我现在明白是什么问题了。

标签: data-structures linked-list


【解决方案1】:

由于快指针的移动速度是慢指针的两倍,因此两个指针之间的距离将始终增加 1(最初它们之间的距离为 2)。

现在假设存在循环,当慢速指针进入循环时,慢速和快速之间的距离为“x”,循环的长度为“d”。所以现在下一次慢速和快速移动,它们之间的距离将变为 x+1,然后在下一次移动之后,它将是 x+2,然后是 x+3,依此类推。只要它们之间的距离是 d 的倍数,快和慢就会相遇。因此,通过将它们之间的距离增加 1,我们正在检查每个值。

现在考虑快速移动速度快三倍的情况,那么它们之间的每一步距离将增加 2,即 x、x+2、x+4 等等。所以这两个指针可能不会相互相遇并相互交叉,如果发生这种情况,您的程序将永远不会终止。 如果快速指针的速度是四倍,五倍等等,情况也是如此。

【讨论】:

    【解决方案2】:

    当较快的指针以较慢指针的两倍以上的速度移动时,它可以与第一个指针重叠。如果每次两个指针通过时都会发生这种情况(在同一个循环内),那么算法将 1. 永远找不到循环,2. 永远不会终止。

    【讨论】:

      【解决方案3】:

      复杂度不是按循环步骤数计算的,而是按访问的节点计算的。 因此,上述算法只是使防止 NullPointerExceptions 更加困难。循环也可以是任意长度的节点。

      在朴素的解决方案中需要一个二次算法:检查所有 j

      更快的算法使用内存。

      要么由一个世代标记(一种引用计数)作为节点的字段。

      // Complexity O(N)
      
      class Node
          Node next;
          boolean generation;
      
      class List
          Node first;
          boolean nodesGeneration; // All nodes false or true
      
          boolean isCircular() {
              for (Node node = first; node != null; node = node.next) {
                  if (node.generation != nodesGeneration) {
                      return false;
                  }
                  node.generation = !node.generation;
              }
              nodesGeneration = !nodesGeneration;
              return true;
          }
      

      或者如果这是不可能的/不希望的:使用一个以对象引用作为键的 IdentityHashMap。就像一组对象引用 (==)。

      // Complexity O(N), worse case O(N . log N)
      
          boolean isCircular() {
              Map<Node, Boolean> checkedNodes = new IdentityHashMap<>();
              for (Node node = first; node != null; node = node.next) {
                  if (checkedNodes.containsKey(node)) {
                      return false;
                  }
                  checkedNodes.put(node, Boolean.TRUE);
              }
              return true;
          }
      

      【讨论】:

        【解决方案4】:

        不仅如此。

        如果循环在链接列表中,程序将更快地找到循环。

        例如使用fast.next.next,while 将在 10000 个节点的链接列表中执行 9998 次。

        但如果最后一个节点指向节点800,则需要9198次才能找到循环

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-11-06
          • 1970-01-01
          • 1970-01-01
          • 2021-10-16
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多