【问题标题】:Circular linked list logic循环链表逻辑
【发布时间】:2017-04-14 05:51:15
【问题描述】:
/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null){
            return false;
        }
          ListNode slow = head;
          ListNode fast = head.next;

          while((slow != null) && (fast != null) && (slow.next != null) && (fast.next != null)){
            if(slow == fast){
                return true;
            }
            slow = slow.next;
            fast = fast.next.next;

          }

          return false;
    }
}

为了检测循环链表,我们使用2指针技术,慢速和快速。

我的问题是,如果列表是循环列表,我怎么知道指针必须在某个点相交?

【问题讨论】:

  • 顺便说一下,循环链表不需要两个指针,主要用于实现队列和斐波那契堆。到达第一个开始的节点时停止遍历。
  • @PorkkoM 问题不是关于使用循环链的链表实现,而是关于检测非循环链表的损坏链。
  • 关于复杂性,为什么不同时拥有链表和集合呢?保持链表中项目的顺序并跟踪索引结构中的重复(循环)。那为什么不使用已经存在的LinkedHashSet<E>。已经实现和测试,如果你想添加任何自定义行为(比如抛出异常而不是在 add() 上返回 false),你可以继承它?
  • @andrei-macarie 双指针方法的一个优点是它不需要额外的内存(除了存储两个指针:-)。此外,它还通过快速指针与慢速指针相交(检测到循环)或快速指针到达列表末尾(无循环)在 O(n) 中找到答案。
  • @qwertyman 我明白了,所以这个问题是关于 - 我更关心内存而不是性能,其中复杂性不超过 O(n)。因此,该算法将与有限数量的元素一起使用,无论如何都会消耗低内存,但对于物联网时代,在便宜的设备上可能值得。现在我明白了:)

标签: java algorithm linked-list


【解决方案1】:

看看手表。这是一个数字 1 到 12 的循环列表,然后循环回到 1。

大手快,小手慢,都在同一个方向,从同一个点开始(top = 12)。

因为列表(边)是圆形的,所以大手最终会追上小手。多快取决于速度差异,但它会赶上。如果赶上,列表必须是循环的。

如果它没有赶上,但到达列表末尾,则列表不是循环的。

即使列表没有回到开头,例如如果12转回9,快手会一直转,直到小手进入圈子,然后快手最终会追上小手。

好的,对于最后一部分,手表的形象不太好,但我希望你明白了。

【讨论】:

  • 这是否适用于所有速度的指针?即,一次慢动作 2 个,一次快速移动 6 个?还是慢动作一次1,快动作一次17?他们必须赶上它的循环吗?
  • @user7487099 是的。正如我所说,快手赶上慢手的速度取决于速度差异,但它会赶上。他们都可以以不同的速度绕同一个圆圈移动,而不会偶尔相遇。
  • @Andreas 我怀疑正如另一个答案提到的那样,如果它们使用正确(错误)的跳转大小,两个指针应该可以同步,这样它们就永远不会真正命中。
  • @Sahuagin 真的。我的回答更多是对他们为什么总是见面的模拟视觉解释。在模拟世界中,快手不能“跳过”慢手。在数字世界中,步数增量确实很重要,如 explained by qwertyman,但问题代码的 1-vs-2 步不是问题。
【解决方案2】:

证据并不像看起来那么明显。

实际上,只要稍加改动就能使快速指针变得更快,例如使用fast = fast.next.next.next,算法不再保证可以工作。

重要的是两个指针的相对速度。

在标准情况下,相对速度为 2 - 1 = 1,这意味着在每一步中,快指针与慢指针接近一个单位。这样就保证了快的一个会追上来,不会跳过另一个。

否则,例如如果相对速度是 3 - 1 = 2,那么它们可能永远不会相交。如果我们从指针之间的奇数距离开始,并且循环长度是偶数,就会发生这种情况。在这种情况下,距离将始终保持奇数(因此永远不会为零)。


为了清楚说明如果不注意速度差异,指针可能不会相交,考虑速度为 3 的快指针和速度为 1 的慢指针,循环运行有 4 个节点,标记为 0、1 , 2, 3,形成这样的循环 0 -> 1 -> 2 -> 3 -> 0。

假设最初,慢速指针位于节点 0,而快速指针位于节点 1。(请注意,这不是一个强假设,并且可能无法通过不同的初始化策略来缓解 - 无论初始化方法如何, 可能是图中还有一些额外的节点,而不是循环的一部分,这使得指针在它们都到达循环后就可能处于任意位置。

k 步骤之后,慢指针将位于节点k mod 4。快速节点将位于节点(1 + 3k) mod 4。假设有一个k 使得快慢指针在同一位置,这意味着(1 + 3k) mod 4 = k mod 4(1 + 2k) mod 4 = 0。但左边是奇数,所以不能为零。因此,指针可以指向同一个节点的假设是矛盾的。

【讨论】:

    【解决方案3】:

    好吧,正如安德烈亚斯所说,看看手表,但如果这仍然没有意义,那么this 可能会有所帮助。

    另外,您可以稍微简化一下代码:

    public boolean isCyclic(Node first) {
        if(first == null) {
          return false;
        }
        Node fast = first;
        Node slow = first;
        while(fast.getNext() != null && fast.getNext().getNext() != null) {
          slow = slow.getNext();
          fast = fast.getNext().getNext();
          if(slow == fast) {
            return true;
          }
        }
        return false;
    }
    

    我也认为你应该用 head 而不是 head.next

    来初始化你的 fast 指针

    【讨论】:

      【解决方案4】:

      下面是C语言循环链表的完整实现:​​

      #include <stdio.h>
      #include <stdlib.h>
      struct node {
      int data;
      struct node* next;
      };
      void insert(struct node** head,int ele){
      struct node* temp = (struct node*)malloc(sizeof(struct node));
      struct node* ptr;
      temp->data = ele;
      temp->next = temp;
      if(*head == NULL){
          *head = temp;
      }else{
          ptr = *head;
          while(ptr->next != *head){
              ptr = ptr->next;
          }
          ptr->next = temp;
          temp->next = *head;
      }
      }
      void deleteLastEle(struct node** head){
      struct node* current = *head;
      struct node* pre = *head;
      while(current->next != *head){
          pre = current;
          current = current->next;
      }
      pre->next = current->next;
      free(current);
      }
      void deleteAtPos(struct node** head,int pos){
      struct node* current = *head;
      struct node* pre = *head;
      for(int i=0;i<pos-1;i++){
          pre = current;
          current = current->next;
      }
      pre->next = current->next;
      free(current);
      }
      void deleteFirst(struct node** head){
      struct node* current = *head;
      struct node* temp = *head;
      while(current->next != *head){
          current = current->next;
      }
      current->next = (*head)->next;
      *head = (*head)->next;
      free(temp);
      }
      printCLL(struct node** head){
      struct node* ptr = *head;
      do{
          printf("%d ",ptr->data);
          ptr = ptr->next;
      }while(ptr != *head);
      printf("\n");
      }
      // main() function.
      int main(void) {
      struct node* head = NULL;
      for(int i=0;i<5;i++){
          //function to insert elements in linked list
          insert(&head,i);
      }
      printf("inseted linkedlist: \n");
      //print the content of linked list.
      printCLL(&head);
      
      //function to delete last element
      deleteLastEle(&head);
      printf("after deleting last element: \n");
      printCLL(&head);
      
      //function to delete element at pos 3.
      deleteAtPos(&head,3);
      printf("after deleting at pos 3: \n");
      printCLL(&head);
      
      //function to delete first element of linkedlust
      deleteFirst(&head);
      printf("after deleting first element: \n");
      printCLL(&head);
      return 0;
      }
      

      这些是输出:

      inseted linkedlist: 
      0 1 2 3 4 
      after deleting last element: 
      0 1 2 3 
      after deleting at pos 3: 
      0 1 3 
      after deleting first element: 
      1 3 
      

      【讨论】:

        猜你喜欢
        • 2011-12-02
        • 2020-02-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-04
        • 2011-06-05
        • 2019-01-08
        相关资源
        最近更新 更多