How to detect a loop (cycle) in a linked list

Traversing linked list is easy but what would happen if there is a cycle in the linked list?
It will just fall into an infinity loop if traversing is implemented without a cycle prevention code.
Thankfully, there is already a known algorithm for this kind of problem.

How to detect if a cycle exists?

Suppose there is a linked list that has a cycle like below.
Now the question is how to detect the cycle.

linked list cycle example

Let’s first think about what would happen if there is a cycle.

Iterating linked list typically happens in a while loop like these steps.

  1. Check if the node is valid (not null)
  2. Process node
  3. Move on to next node and do #1,2

Normally iteration will be done once it reaches the end of the list of which next node is null.
However, it will fall into an infinite loop if there is a cycle since there won’t be any null node.

Then, the key is that we shouldn’t visit a node if it was already visited.
How do we detect visited nodes?
The easy answer is to have a cache (map, array or whatever) that keeps track of visited nodes.

So there will be an additional step for cycle detection

  1. Check if the node is valid (not null)
  2. Process node
  3. Move on to next node
  4. Check if this node is already visited. If yes then there is a cycle.

What is the time complexity of this problem? O(n). We only need to iterate once.
What is the space complexity of this problem? O(n). Potentially we need to keep track of all visited nodes.

Can we do better?

We cannot do better on time complexity because you need to iterate the list at least once.
However, there is definitely a way to improve space complexity.
In fact, we can solve this problem with O(1) space complexity.

How can we do that?
Think about two people running on a circular track.
And let’s say one person is able to run twice faster than the other.
Then what would happen in that case?
The faster runner will catch the slow runner at the start point after 1 lap of slow person since the faster runner is able to run track twice while the slow runner finishes 1 lap.
We can apply exact same principle to detect a cycle in a linked list.
If we have two pointers that one pointer iterates twice faster than the other faster pointer will catch up slower pointer once it completes 1 lap.

How to find out where the cycle starts?

I think some visualization will do a much better job than a verbal explanation.
So let’s take a look.

linked list cycle example

The cycle may start after a few nodes.
But as you can see in the picture, the number of nodes before cycle essentially means the number of nodes before cycle start node where two pointers begin iteration.

The above picture shows you that node 15 will be the location where two pointers will meet.
Then, all we need to do is have another pointer at the start of the linked list and iterate both pointer 1 (slow pointer) and 3rd pointer until they meet which is the cycle node!

Code Example

struct Node 
{
    int val;
    Node *next;
    Node(int x) : val(x), next(0) {}
};

Node *detectCycle(Node *head) 
{
    if (!head)
    {
        return 0;
    }

    Node *iter1 = head->next;
    Node *iter2 = head->next ? head->next->next : 0;

    while (iter1 && iter2)
    {
        // cycle found!
        if (iter1 == iter2)
        {
            break;
        }

        iter1 = iter1->next;
        iter2 = iter2->next ? iter2->next->next : 0;
    }

    // no cycle
    if (!iter1 || !iter2)
    {
        return 0;
    }

    Node *cycleNode = head;

    while (iter1 != cycleNode)
    {
        iter1 = iter1->next;
        cycleNode = cycleNode->next;
    }

    return cycleNode;
}

Intro to Linked List data structure

Today, we will go over a popular data structure – linked list.
Linked list may not make much sense for some languages such as python and javascript since there is already a good one such as list.
However, since linked list is quite useful for languages like C++ it is still worthwhile to go over.
In this post I will go over some important characteristics about linked list and strengths and weaknesses.
Please note that all the explanations will be based on C++.

What is Linked List?

Linked list is a data structure that each node is connected like a chain via pointer.
It is one of fundamental data structure in C++ and widely used in C++ standard library (STL).

I believe a picture is worth a thousand words and let’s take a look at how a linked list looks like before any explanations.

As you see above there are three nodes in the linked list.
Each node is based on Node struct as below which contains a value and a pointer to the next Node.

struct Node
{
    // value that this Node contains. 
    // It could be any data type you want to contain
    int   val;
    
    // pointer to next node
    Node *next;
    
    // pointer to previous node.
    // this is optional
    // Node *prev;
};

The example code has only one pointer ‘next’ but you can also have another Node * to point to the previous Node.
If you have only one pointer (likely it’s a pointer to the next Node) it’s called a singly linked list.
If you have two pointers one pointing to the next and the other to the previous then it’s called a doubly linked list.

Linked List Operations – insert, search, delete

As you can imagine there are three basic operations for a linked list.
You need to be able to search a node in the list, insert and delete.
Let’s go over each operation.

Search

How should the search be?
Given you have a value to search you need to start from the head of the list and check if each node in a chain is the same as the search value.
Return a pointer to the node if you find it or null pointer otherwise.
Let’s take a look at code example for search.

/**
 * @brief  search a node based on search value
 * 
 * @param  head       head of linked list
 * @param  searchVal  value to search in the list
 * 
 * @return pointer to found node. null otherwise
 */
Node *searchNode(Node *head, int searchVal)
{
    // end of the list. value not found
    if (!head)
    {
        return 0;
    }

    if (head->val == serachVal)
    {
        return head;
    }
    else
    {
        return searchNode(head->next, searchVal);
    }
}

I implemented the operation recursively so I don’t need to write a loop. (I thought the recursive solution was easier than loop but it’s your choice)
I actually also used loop based implementation for insertion operation for your reference.

Insertion

Inserting a node could be slightly trickier depending on how you want to maintain the list.
There are three possible cases for insertion.

1. Insert the node at the head of the list
This one is the easiest as you just need to create a node and insert the node.
Here is the code example for head insertion.

/**
 * @brief  node insertion at the beginning of the list
 * 
 * @param  head    double pointer to head since you need to update 'head' after insertion
 * @param  newVal  new value to insertNode
 */
void insertNode(Node **head, int newVal)
{
    Node *newNode = new Node;
    newNode->val = newVal;
    newNode->next = *head;

    // update head
    *head = newNode;
}

2. Insert the node at the tail of the list
This one is slightly harder than head insertion but still pretty simple.
What you need to do is to traverse to the end of the list and insert the node there.

/**
 * @brief  node insertion at the end of the list
 * 
 * @param  head    double pointer to head since you still might need to update 'head'
 *                 if the list is empty
 * @param  newVal  new value to insert
 */
void insertNode(Node **head, int newVal)
{
    Node *newNode = new Node;
    newNode->val = newVal;
    newNode->next = 0;

    Node *tailNode = *head;
    while (tailNode && tailNode->next)
    {
        tailNode = tailNode->next;
    }

    if (!tailNode)
    {
        *head = newNode;
    }
    else
    {
        tailNode->next = newNode;
    }
}

3. Insert the node based on the sorted order of the list.
Imagine you want to maintain the list in sorted order (decreasing or increasing).
Then you will need to find a proper location to insert.

For example, let’s say you have a list like below and you would like to insert value 4.
1-> 3-> 5

Then you need to traverse the list and find a node that is not less than 4 which is 5, the last one, and create a node and insert.
However, there is a couple of other edge cases such as the inserting position happens to be at the head or tail of the list.
Although this one is not terribly difficult it does require little more thoughts than the others.

void insertNode(Node **head, int newVal)
{
    // create new Node
    Node *newNode = new Node;
    newNode->val = newVal;
    newNode->next = 0;
    
    // if empty list then just insert it here
    if (!*head)
    {
        *head = newNode;
        return;
    }
    // check if it needs to be inserted at head.
    else if (newNode->val < (*head)->val)
    {
        newNode->next = *head;
        *head = newNode;
        return;
    }
    
    Node *iter = *head;
    
    // loop until you find first Node that is not less than new value
    while (iter && iter->next)
    {
        if (iter->next->val >= newNode->val)
        {
            break;
        }
        
        iter = iter->next;
    }
    
    if (iter->next)
    {
        newNode->next = iter->next;
    }
    
    iter->next = newNode;
}

The tricky part of insertion is that you have to make sure you update the next pointers properly.
1. If insertion is happening at the head, make sure head pointer is updated and the next pointer of new node point to last head
2. If insertion is happening at the tail, make sure the last node of the list is pointing to the new node properly
3. If insertion is happening in the middle of the list, make sure the previous node’s next points to new node and new node points to the proper next one.

deletion

The deletion of a node is a very similar process as insertion.
First, you need to find which node to delete if there is any and properly updates the next pointers.
Just like insertion deleting node could be the head, the tail or in the middle of the list.
Let’s take a look at the code example.

/**
 * @brief  delete a node from the list if there is one
 *         for simplicity I don't delete all the duplicate values here
 * 
 * @param  head       double pointer to head in case you have to delete head node
 * @param  deleteVal  value to delete
 */
void deleteNode(Node **head, int deleteVal)
{
    Node *iter = *head;
    
    // nothing to delete
    if (!*head)
    {
        return;
    }
    // deleting head. need to update head
    else if ((*head)->val == deleteVal)
    {
        *head = (*head)->next;
        delete iter;
        return;
    }

    while (iter && iter->next)
    {
        if (iter->next->val == deleteVal)
        {
            break;
        }

        iter = iter->next;
    }

    if (iter->next)
    {
        Node *delNode = iter->next;
        iter->next = iter->next->next;
        delete delNode;
    }
}

Pros

  • Unlike an array size of a linked list is very flexible as long as the memory permits
  • Insertion and deletion are much simpler than an array. For an array insertion/deletion you have to move many other elements after the operation
  • It is a perfect data structure to implement more complex data structures like Stack and Queue since you only need to maintain head(or tail) of the list for insertion, search, deletion

Cons

  • It requires extra memory (next, prev pointers)
  • It does not allow random access like an array and therefore search could be slower than an array

Performance

Search – O(n)
Insertion – O(1) if always insert at the head (for cases like Stack)
O(n) otherwise
Deletion – O(1) if always delete head (for cases like Stack)
O(n) otherwise

Conclusion

We have taken a look at some basics of linked list.
Linked list is a great data structure for some special purposes due to its flexibility of insertion and deletion.
However, there are some weaknesses about it so it requires some discretion to wisely use it.

Thank you for reading my post and please let me know if you have any questions or suggestions and good luck!