3
/**
 * 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;
    }
}

For detecting a circular linked list, we use the 2 pointer technique, slow and fast.

My question is, how do I know the pointers must intersect at some point if the list is a circular list?

5
  • By the way there is no need for two pointers in Circular linked list.it is mainly used for implementing queue and Fibonacci heap.You stop traversing when you reached first node where you have started. Commented Apr 14, 2017 at 5:58
  • 1
    @PorkkoM The question is not about the linked list implementation that uses a circular chain, but about detecting a corrupt chain for a non-circular list. Commented Apr 14, 2017 at 6:02
  • Regarding complexity, why not having a linked list and a set at the same time? Kepping the order of items in a linked list and keep track of repetitions (cycles) in an indexed structure. Then why not using the already existing LinkedHashSet<E>. Is already implemented and tested and you can inherit from it if you want to add any custom behavior (like throwing exceptions instead of returning false on add())? Commented Apr 14, 2017 at 6:31
  • 1
    @andrei-macarie one advantage of the two pointer method is that it requires no additional memory (except for storing the two pointers :-). Furthermore, it also finds the answer in O(n), by either the fast pointer intersecting the slow pointer (cycle detected), or the fast pointer reaching the end of the list (no cycle). Commented Apr 14, 2017 at 7:06
  • @qwertyman I see, so this question is about - I care more about memory and less about performance, where the complexity is not more than O(n). So the algorithm is to be used with limited number of elements, consuming low memory anyway, but for IOT era, well on a cheap device it might worth it. Now I get it :) Commented Apr 14, 2017 at 7:12

4 Answers 4

2

Look at a watch. That is a circular list of numbers 1 to 12 then circles back to 1.

The big hand is moving fast, the small hand is moving slow, both moving in the same direction, and starting at the same point (top = 12).

Because the list (edge) is circular, the big hand will eventually catch back up to the small hand. How quickly depends on the speed difference, but it will catch up. If it catches up, the list must be circular.

If it doesn't catch up, but gets to end of list, the list is not circular.

Even if the list doesn't circle back to the beginning, e.g. if 12 circled back to 9, the fast hand would just keep circling, until the small hand enters the circle, and then the fast hand will eventually catch up to the small hand.

Ok, for that last part, the image of a watch wasn't good, but I hope you got the point.

Sign up to request clarification or add additional context in comments.

4 Comments

Does this hold for pointers of all speeds? Ie, slow moves 2 at a time, and fast moves 6 at a time? Or slow moves 1 at a time and fast move 17 at a time? They must catch up if its circular?
@user7487099 Yes. As I said, how quickly the fast hand catches up to the slow hand depends on the speed difference, but it will catch up. They can both be moving at different speeds around the same circle, without meeting occasionally.
@Andreas I suspect that as the other answer mentions, it should be possible for the two pointers to be synchronized such that they will never actually hit, if they used the right (wrong) jump sizes.
@Sahuagin True. My answer is more of an analog visual explanation of why they would always meet up. In the analog world, the fast hand cannot "jump over" the slow hand. In the digital world, step increments do matter, as explained by qwertyman, but isn't an issue with the 1-vs-2 step of the question code.
2

The proof is not as obvious as it might seem.

Actually, with a little change that would make the fast pointer even faster, e.g. using fast = fast.next.next.next, the algorithm is no longer guaranteed to work.

What matters is the relative speed of the two pointers.

In the standard case, the relative speed is 2 - 1 = 1, which means that at each step, the fast pointer gets one unit closer to the slow pointer. In this way, it is guaranteed that the fast one will catch up and not jump over the other.

Otherwise, e.g. if the relative speed is 3 - 1 = 2, then it is possible for them to never intersect. This would occur if we start with an odd distance between the pointers, and the cycle length is even. In this case, the distance always will always remain odd (thus it will never be zero).


To make it clear that the pointers may not intersect if not being careful about the speed difference, consider a fast pointer with speed 3, and a slow pointer with speed 1, running in a cycle with 4 nodes, labeled 0, 1, 2, 3, forming a cycle like this 0 -> 1 -> 2 -> 3 -> 0.

Assume that initially, the slow pointer is at node 0 and the fast pointer is at node 1. (note that this is not a strong assumption, and may not be alleviated by a different initialization strategy -- regardless of the initialization method, it might be the case that there are some additional nodes in the graph, not part of the cycle, making it possible for the pointers to be in arbitrary positions once they both reach the cycle).

After k steps, the slow pointer will be at node k mod 4. The fast node will be at node (1 + 3k) mod 4. Assuming there is a k such that the fast and slow pointer are at the same position, it means (1 + 3k) mod 4 = k mod 4, i.e. (1 + 2k) mod 4 = 0. But the left hand side is an odd number, thus it can not be zero. Therefore, the assumption that the pointers can point at the same node lead to a contradiction.

Comments

0

Well, as andreas mentioned look at the watch but if that still doesn't make sense make this could help.

Also, you can simplify your code a bit:

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;
}

I also think that you should initialize your fast pointer with head instead of head.next

Comments

-1

Here is the complete implementation of circular linked list in 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;
}

And these are the outputs:

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 

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.