【问题标题】:Cracking the Coding Interview, 6th edition, 2.8破解编码面试,第 6 版,2.8
【发布时间】:2015-12-06 06:24:39
【问题描述】:

问题陈述:给定一个循环链表,实现一个返回循环开始节点的算法。

答案提供了一个比我建议的更复杂的解决方案。我的怎么了?:

public static Node loopDetection(Node n1) {
    ArrayList<Node> nodeStorage = new ArrayList<Node>();

   while (n1.next != null) {
       nodeStorage.add(n1);
       if (nodeStorage.contains(n1.next)) {
           return n1;
       }
       else {
           n1 = n1.next;
       }
   }

   return null;
}

【问题讨论】:

  • 我很困惑。在循环链表中,您将保留对列表“头”的引用,并且头是循环中的“第一个”节点,因此它位于循环的开头,因此 return head ( O(1))。或者,如果您唯一的参考是列表的“尾部”,则头部位于“tail.next”,同样是一个简单的返回语句。
  • @Andreas:考虑一个列表 A->B->C->D->E->C->D->E->... 循环的开始在 C,不是 A。带有循环的链表没有尾巴。想象一下在没有任何出口的单向道路上行驶到环形交叉路口。
  • @gnasher729 circular linked list 与一个 corrupt 非圆形(又名开放或线性)之间存在差异,其中最后一个节点指向第一个节点带有循环的链表。我现在可以看到,问题实际上不是关于 circular 链表,而是关于 corrupt 线性链表,因此我最初的困惑。 “问题陈述”措辞不当。

标签: java algorithm linked-list big-o time-complexity


【解决方案1】:

我无法想象这个算法发生了什么。希望这对其他人有帮助。

在时间 t = k(3) 时,p2 到 head(0) 的距离是 p1 的两倍,所以为了让它们重新排成一行,我们需要 p2“赶上”p1,它需要 L - k(8) 再发生 5 个步骤。 p2 的速度是 p1 的 2 倍。

在时间 t = k + (L - k) (8) 时,p2 需要向前移动 k 步才能回到 k。如果我们将 p1 重置为 head(0),我们知道如果 p2 以与 p1 相同的速度行进,那么 p1 和 p2 将在 k(3, 19) 处相遇。

【讨论】:

    【解决方案2】:

    有阿米特给出的解决方案。问题是你要么知道,要么不知道,但你无法在面试中弄清楚。因为我从来没有需要在链表中找到一个循环,所以除了通过面试之外,知道它对我来说毫无意义。因此,对于面试官来说,将此作为一个面试问题,并期待 amir 的回答(这很好,因为它具有线性时间和零额外空间),这是非常愚蠢的。

    所以您的解决方案大部分都很好,除了您应该使用哈希表,并且您必须确保哈希表散列对节点的引用而不是节点。假设您有一个包含字符串和“next”指针的节点,哈希函数对字符串进行哈希处理,如果字符串相等,则比较节点是否相等。在这种情况下,除非您小心,否则您会找到第一个带有重复字符串的节点,而不是循环开始的节点。

    (amir 的解决方案在 == 比较对象而不是引用的语言中存在非常相似的问题。例如在 Swift 中,您必须使用 ===(比较引用)而不是 ==(比较对象))。

    【讨论】:

    • 你是对的,但现实是招聘人员会继续问这样的问题......
    • 正如您所提到的,我想出的解决方案是使用参考哈希表。我想这个解决方案与书中给出的解决方案相比的缺点是 O(n) 空间与 O(1) 空间;都是 O(n) 时间
    【解决方案3】:

    你的解决方案是O(n^2)时间(ArrayList中的每个contains()O(n)时间)和O(n)空间(用于存储nodeStorage),而“更复杂”的解决方案是O(n)时间和O(1) 空格。

    本书提供了以下解决方案,感兴趣的人可以参考,O(n)时间和O(1)空间:

    如果我们移动两个指针,一个速度为 1,另一个速度为 2, 如果链表有循环,他们最终会相遇。为什么?思考 大约有两辆汽车在一条赛道上行驶——速度更快的汽车总是会超过 慢一个!这里棘手的部分是找到循环的开始。 想象一下,作为一个类比,两个人在赛道上比赛,一个人在跑步 速度是另一个的两倍。如果他们从同一个地方开始,当 他们下次见面吗?他们将在下一圈开始时相遇。 现在,假设 Fast Runner 在 n 上领先 k 米 一步一圈。他们下次见面什么时候?他们将在开赛前相遇公里 下一圈的开始。 (为什么?Fast Runner 会得到 k + 2(n - k) 步骤,包括它的领先优势,Slow Runner 会取得 n - k 脚步。两者都将在循环开始前 k 步。)现在,继续 回到问题,当 Fast Runner (n2) 和 Slow Runner (n1) 在我们的循环链表中移动,n2 将领先于 n1 进入时的循环。具体来说,它将具有 k 的领先优势, 其中 k 是循环前的节点数。因为 n2 有一个头 k 个节点的开始,n1 和 n2 将在开始之前遇到 k 个节点 环形。所以,我们现在知道以下内容: 1. Head 是来自 LoopStart 的 k 个节点(根据定义)。 2. n1 和n2 的MeetingPoint 是来自LoopStart 的k 个节点(如上所示)。因此,如果我们将 n1 移回 Head 并将 n2 保留在 MeetingPoint, 并以相同的速度移动它们,它们将在 LoopStart 相遇。

    LinkedListNode FindBeginning(LinkedListNode head) {
       LinkedListNode n1 = head;
       LinkedListNode n2 = head;
    
       // Find meeting point
       while (n2.next != null) {
          n1 = n1.next;
          n2 = n2.next.next;
          if (n1 == n2) {
             break;
          }
       }
    // Error check - there is no meeting point, and therefore no loop
       if (n2.next == null) {
          return null;
       }
       /* Move n1 to Head. Keep n2 at Meeting Point. Each are k steps
       /* from the Loop Start. If they move at the same pace, they must
       * meet at Loop Start. */
       n1 = head;
       while (n1 != n2) {
          n1 = n1.next;
          n2 = n2.next;
       }
       // Now n2 points to the start of the loop.
       return n2;
       }
    

    【讨论】:

    • 您能否在回答中也提供O(n) 解决方案?
    • @TimBiegeleisen O(n) 时间和空间将只使用 HashSet 而不是 ArrayList。要获得恒定的时间,您需要书中描述的“技巧”(见编辑)
    • @amit 您的意思是该评论中的恒定空间吗?恒定时间几乎肯定是不可能的。
    • @moreON 当然是线性时间和常数空间。然而,答案本身确实正确地提到了它。
    • 在您的第一个 while 循环中,不应该是 while ( n2 != null and n2.next != null) 吗?以下if 相同。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-12
    • 2017-08-31
    • 2012-09-24
    • 2018-08-12
    • 2018-11-21
    • 2013-09-05
    相关资源
    最近更新 更多