Facebook Pixel

708. Insert into a Sorted Circular Linked List 🔒

Problem Description

You are given a circular linked list that is sorted in non-descending order (ascending order with possible duplicates). Your task is to insert a new value insertVal into this list while maintaining the sorted order.

Key points about the problem:

  1. Circular Linked List: The last node points back to the first node, forming a circle with no null references in the next pointers.

  2. Sorted in Non-Descending Order: The values are arranged such that each node's value is less than or equal to the next node's value (e.g., 1 → 2 → 3 → 3 → 4 → 1... where the last 1 connects back to the first).

  3. Given Node: You receive a reference to any single node in the list, not necessarily the smallest or first node.

  4. Insertion Rules:

    • Insert insertVal at any suitable position that maintains the sorted order
    • If multiple valid positions exist, you can choose any one
    • The list should remain circular and sorted after insertion
  5. Edge Cases:

    • If the list is empty (head is null), create a new single-node circular list containing insertVal and return it
    • Otherwise, return the original head node that was given

The solution needs to handle three main scenarios for insertion:

  • Normal case: Insert between two nodes where prev.val ≤ insertVal ≤ curr.val
  • Boundary case: Insert at the transition point where the largest value connects back to the smallest value (when prev.val > curr.val)
  • Empty list case: Create a new circular list with just one node

The algorithm traverses the circular list to find the appropriate insertion point, creates a new node with insertVal, and adjusts the pointers to maintain the circular structure while preserving the sorted order.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

To understand how to insert into a sorted circular linked list, let's think about what makes this problem unique. In a regular sorted linked list, we'd simply find where the value fits and insert it. But in a circular list, there's no clear "beginning" or "end" - it's a continuous loop.

The key insight is recognizing that even though the list is circular, there are only a few distinct positions where we can insert a value:

  1. Between two nodes in the sorted sequence: This is the most straightforward case. If we have nodes with values like 3 → 5 → 7, and we want to insert 6, it clearly goes between 5 and 7.

  2. At the "wrap-around" point: In a circular sorted list, there's exactly one place where a larger value points to a smaller value - where the maximum connects back to the minimum. For example, in 1 → 3 → 5 → 1..., the transition from 5 to 1 is special. This is where we'd insert values that are either larger than the maximum or smaller than the minimum.

The challenge is that we might start traversing from any node, not necessarily the smallest one. So we need to keep moving through the list looking for one of these insertion points.

Our strategy becomes:

  • Use two pointers (prev and curr) to examine consecutive pairs of nodes
  • Check if insertVal fits between them: prev.val ≤ insertVal ≤ curr.val
  • If not, check if we're at the wrap-around point (prev.val > curr.val), and if so, whether insertVal should go there (either it's bigger than the max or smaller than the min)
  • Keep moving forward until we find the right spot or complete a full circle

The beauty of this approach is that we're guaranteed to find a valid insertion point within one complete traversal of the list. If we've gone all the way around without finding a spot, it means all values in the list are the same, and we can insert anywhere - so we just insert after the head.

For an empty list, we simply create a self-referencing node (pointing to itself) to maintain the circular property.

Learn more about Linked List patterns.

Solution Approach

Let's walk through the implementation step by step:

1. Handle the Empty List Case

if head is None:
    node.next = node
    return node

When the list is empty, we create a new node that points to itself, forming a single-node circular list.

2. Set Up Two Pointers for Traversal

prev, curr = head, head.next

We initialize two pointers: prev starts at the given head node, and curr starts at the next node. These will help us examine consecutive pairs of nodes to find the insertion point.

3. Traverse the Circular List

while curr != head:

We continue traversing until curr comes back to head, meaning we've completed one full circle through the list.

4. Check for Valid Insertion Points

Inside the loop, we check two conditions:

if prev.val <= insertVal <= curr.val or (
    prev.val > curr.val and (insertVal >= prev.val or insertVal <= curr.val)
):
    break

First condition: prev.val <= insertVal <= curr.val

  • This handles the normal case where insertVal fits between two consecutive nodes in the sorted sequence
  • For example, if we have 3 → 5 and want to insert 4, this condition is satisfied

Second condition: prev.val > curr.val and (insertVal >= prev.val or insertVal <= curr.val)

  • The first part prev.val > curr.val identifies the wrap-around point (where max connects to min)
  • At this special point, we insert if:
    • insertVal >= prev.val: The value is greater than or equal to the maximum (goes after the max)
    • insertVal <= curr.val: The value is less than or equal to the minimum (goes before the min)
  • For example, in a list 2 → 4 → 1 → 2..., the transition 4 → 1 is the wrap-around point. We'd insert 5 or 0 here.

5. Move Pointers Forward

prev, curr = curr, curr.next

If we haven't found the insertion point yet, we advance both pointers to check the next pair of nodes.

6. Perform the Insertion

prev.next = node
node.next = curr
return head

Once we find the correct position (or complete a full traversal), we insert the new node between prev and curr by:

  • Making prev point to the new node
  • Making the new node point to curr
  • Returning the original head reference

Edge Case Handling: If all nodes have the same value (e.g., 3 → 3 → 3 → 3...), the loop completes without breaking, and we insert after head by default, which maintains the sorted property since all values are equal.

The time complexity is O(n) where n is the number of nodes, as we traverse at most once through the entire list. The space complexity is O(1) as we only use a constant amount of extra space for the new node and pointers.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through inserting value 4 into a circular sorted linked list: 1 → 3 → 5 → 1... (where 5 points back to 1).

Suppose we're given a reference to the node with value 3 as our head.

Initial Setup:

  • Create new node with value 4
  • Set prev = 3 (head) and curr = 5 (head.next)

First Iteration:

  • Check: Is 3 ≤ 4 ≤ 5? Yes! ✓
  • This is our insertion point - break from loop

Insertion:

  • Set prev.next = node(4) → Now 3 points to 4
  • Set node.next = curr → Now 4 points to 5
  • Final list: 1 → 3 → 4 → 5 → 1...
  • Return the original head (node 3)

Another Example - Wrap-around Case:

Insert value 6 into the same list: 1 → 3 → 5 → 1..., starting from node 3.

Iteration 1:

  • prev = 3, curr = 5
  • Check: Is 3 ≤ 6 ≤ 5? No
  • Move forward: prev = 5, curr = 1

Iteration 2:

  • prev = 5, curr = 1
  • Check: Is 5 ≤ 6 ≤ 1? No
  • But wait! Is 5 > 1? Yes - this is the wrap-around point
  • Is 6 ≥ 5? Yes! ✓
  • This is our insertion point - break

Insertion:

  • Insert 6 between 5 and 1
  • Final list: 1 → 3 → 5 → 6 → 1...

The algorithm correctly identifies that 6 should go after the maximum value 5, right at the wrap-around point where the list cycles back to the minimum.

Solution Implementation

1"""
2# Definition for a Node.
3class Node:
4    def __init__(self, val: int = None, next: 'Node' = None):
5        self.val = val
6        self.next = next
7"""
8
9from typing import Optional
10
11
12class Solution:
13    def insert(self, head: Optional['Node'], insertVal: int) -> 'Node':
14        # Create new node to insert
15        new_node = Node(insertVal)
16      
17        # Handle empty list case
18        if head is None:
19            new_node.next = new_node  # Point to itself for circular list
20            return new_node
21      
22        # Initialize pointers for traversal
23        prev_node = head
24        curr_node = head.next
25      
26        # Traverse the circular linked list to find insertion position
27        while curr_node != head:
28            # Check if we found the correct position to insert
29            # Case 1: Insert between two nodes in ascending order
30            if prev_node.val <= insertVal <= curr_node.val:
31                break
32          
33            # Case 2: Insert at the boundary (max/min point)
34            # This happens when prev > curr (we're at the wrap-around point)
35            # Insert if value is greater than max or less than min
36            if prev_node.val > curr_node.val:
37                if insertVal >= prev_node.val or insertVal <= curr_node.val:
38                    break
39          
40            # Move to next pair of nodes
41            prev_node = curr_node
42            curr_node = curr_node.next
43      
44        # Insert the new node between prev_node and curr_node
45        prev_node.next = new_node
46        new_node.next = curr_node
47      
48        return head
49
1/*
2// Definition for a Node.
3class Node {
4    public int val;
5    public Node next;
6
7    public Node() {}
8
9    public Node(int _val) {
10        val = _val;
11    }
12
13    public Node(int _val, Node _next) {
14        val = _val;
15        next = _next;
16    }
17};
18*/
19
20class Solution {
21    /**
22     * Inserts a value into a sorted circular linked list.
23     * 
24     * @param head The reference node in the circular linked list
25     * @param insertVal The value to be inserted
26     * @return The reference node of the list after insertion
27     */
28    public Node insert(Node head, int insertVal) {
29        // Create new node with the value to insert
30        Node newNode = new Node(insertVal);
31      
32        // Handle empty list case
33        if (head == null) {
34            // Create a single-node circular list
35            newNode.next = newNode;
36            return newNode;
37        }
38      
39        // Initialize two pointers to traverse the list
40        Node previous = head;
41        Node current = head.next;
42      
43        // Traverse the circular list to find the insertion position
44        while (current != head) {
45            // Check if we found the correct insertion position
46            if (shouldInsertBetween(previous, current, insertVal)) {
47                break;
48            }
49          
50            // Move both pointers forward
51            previous = current;
52            current = current.next;
53        }
54      
55        // Insert the new node between previous and current
56        previous.next = newNode;
57        newNode.next = current;
58      
59        return head;
60    }
61  
62    /**
63     * Determines if a value should be inserted between two nodes.
64     * 
65     * @param previous The previous node
66     * @param current The current node
67     * @param insertVal The value to be inserted
68     * @return true if the value should be inserted between previous and current
69     */
70    private boolean shouldInsertBetween(Node previous, Node current, int insertVal) {
71        // Case 1: Normal ascending order position
72        // Insert if value fits between previous and current in ascending order
73        if (previous.val <= insertVal && insertVal <= current.val) {
74            return true;
75        }
76      
77        // Case 2: At the boundary between max and min values
78        // This happens when previous > current (end/start of sorted sequence)
79        if (previous.val > current.val) {
80            // Insert if value is greater than max or less than min
81            if (insertVal >= previous.val || insertVal <= current.val) {
82                return true;
83            }
84        }
85      
86        return false;
87    }
88}
89
1/*
2// Definition for a Node.
3class Node {
4public:
5    int val;
6    Node* next;
7
8    Node() {}
9
10    Node(int _val) {
11        val = _val;
12        next = NULL;
13    }
14
15    Node(int _val, Node* _next) {
16        val = _val;
17        next = _next;
18    }
19};
20*/
21
22class Solution {
23public:
24    Node* insert(Node* head, int insertVal) {
25        // Create new node with the value to be inserted
26        Node* newNode = new Node(insertVal);
27      
28        // Handle empty list: create a single-node circular list
29        if (!head) {
30            newNode->next = newNode;
31            return newNode;
32        }
33      
34        // Initialize two pointers to traverse the circular linked list
35        Node* prev = head;
36        Node* curr = head->next;
37      
38        // Find the correct position to insert the new node
39        while (curr != head) {
40            // Case 1: Insert between two nodes in ascending order
41            // prev->val <= insertVal <= curr->val
42            if (prev->val <= insertVal && insertVal <= curr->val) {
43                break;
44            }
45          
46            // Case 2: Insert at the boundary (max to min transition)
47            // prev->val > curr->val indicates we're at the boundary
48            // Insert if insertVal is >= max (prev->val) or <= min (curr->val)
49            if (prev->val > curr->val && 
50                (insertVal >= prev->val || insertVal <= curr->val)) {
51                break;
52            }
53          
54            // Move to the next pair of nodes
55            prev = curr;
56            curr = curr->next;
57        }
58      
59        // Insert the new node between prev and curr
60        prev->next = newNode;
61        newNode->next = curr;
62      
63        return head;
64    }
65};
66
1// Definition for a Node
2class Node {
3    val: number;
4    next: Node | null;
5
6    constructor(val?: number, next?: Node | null) {
7        this.val = (val === undefined ? 0 : val);
8        this.next = (next === undefined ? null : next);
9    }
10}
11
12function insert(head: Node | null, insertVal: number): Node | null {
13    // Create new node with the value to be inserted
14    const newNode = new Node(insertVal);
15  
16    // Handle empty list: create a single-node circular list
17    if (!head) {
18        newNode.next = newNode;
19        return newNode;
20    }
21  
22    // Initialize two pointers to traverse the circular linked list
23    let prev: Node = head;
24    let curr: Node = head.next as Node;
25  
26    // Find the correct position to insert the new node
27    while (curr !== head) {
28        // Case 1: Insert between two nodes in ascending order
29        // The new value fits naturally between prev and curr
30        if (prev.val <= insertVal && insertVal <= curr.val) {
31            break;
32        }
33      
34        // Case 2: Insert at the boundary (max to min transition)
35        // When prev > curr, we're at the wraparound point
36        // Insert if the value is greater than max or less than min
37        if (prev.val > curr.val && 
38            (insertVal >= prev.val || insertVal <= curr.val)) {
39            break;
40        }
41      
42        // Move to the next pair of nodes
43        prev = curr;
44        curr = curr.next as Node;
45    }
46  
47    // Insert the new node between prev and curr
48    prev.next = newNode;
49    newNode.next = curr;
50  
51    return head;
52}
53

Time and Space Complexity

Time Complexity: O(n) where n is the number of nodes in the circular linked list.

The algorithm traverses the circular linked list at most once. In the worst case, when the insertion point is right before the head node, the while loop will iterate through all n nodes in the list before finding the correct position to insert. Each iteration performs constant time operations (comparisons and pointer assignments), so the overall time complexity is linear.

Space Complexity: O(1)

The algorithm uses only a constant amount of extra space. It creates one new node for insertion and uses two pointer variables (prev and curr) to traverse the list. These space requirements do not depend on the size of the input list, making the space complexity constant.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Not Handling the Single-Node List Case Properly

Pitfall: When the circular list contains only one node (where head.next == head), the standard traversal logic with curr = head.next will immediately satisfy the condition curr == head, causing the while loop to never execute. This means the insertion logic inside the loop is never checked, and the new node is always inserted after head, which may not be correct.

Example: If we have a single node with value 3 and want to insert 1, the value 1 should logically come before 3 in a sorted list, but the code would insert it after 3.

Solution: Add a special check for single-node lists:

# After handling empty list
if head.next == head:
    # Single node case - always insert after head
    head.next = new_node
    new_node.next = head
    return head

However, in a circular sorted list, any position maintains the sorted property since it's circular, so this is more of a logical consideration than a bug.

2. Incorrectly Identifying the Wrap-Around Point

Pitfall: The condition prev_node.val > curr_node.val alone isn't sufficient to handle all edge cases at the boundary. Consider a list where all values are the same (e.g., 3 → 3 → 3). In this case, prev_node.val > curr_node.val will never be true, and if insertVal is different from these values (say 5 or 1), it might not find the correct insertion point.

Solution: Ensure the traversal completes a full circle even when no explicit boundary is found:

# Add a flag to track if we've found an insertion point
insert_position_found = False

while curr_node != head:
    if prev_node.val <= insertVal <= curr_node.val:
        insert_position_found = True
        break
  
    if prev_node.val > curr_node.val:
        if insertVal >= prev_node.val or insertVal <= curr_node.val:
            insert_position_found = True
            break
  
    prev_node = curr_node
    curr_node = curr_node.next

# If no position found (all same values), insert anywhere is valid
prev_node.next = new_node
new_node.next = curr_node

3. Boundary Value Comparison Errors

Pitfall: Using strict inequalities (< and >) instead of <= and >= can cause issues with duplicate values. For example, if we have 2 → 3 → 4 and want to insert 3, using prev_node.val < insertVal < curr_node.val would miss the valid insertion position between the first 2 and 3.

Solution: Always use inclusive comparisons (<= and >=) to handle duplicates correctly:

# Correct: handles duplicates
if prev_node.val <= insertVal <= curr_node.val:
    break

# Incorrect: might miss valid positions with duplicates
if prev_node.val < insertVal < curr_node.val:
    break

4. Infinite Loop Risk

Pitfall: If the circular list structure is corrupted (not properly circular) or if there's a bug in pointer advancement, the traversal could run indefinitely.

Solution: Add a safety mechanism to detect if we've traversed more nodes than expected:

max_iterations = 0
curr_node = head.next

# Count nodes first (optional safety check)
temp = head.next
while temp != head and max_iterations < 10000:  # arbitrary large limit
    max_iterations += 1
    temp = temp.next

if max_iterations >= 10000:
    # Handle corrupted list
    return head

# Now traverse with confidence
iterations = 0
while curr_node != head and iterations < max_iterations:
    # ... insertion logic ...
    iterations += 1
    prev_node = curr_node
    curr_node = curr_node.next

These pitfalls highlight the importance of thorough testing with edge cases including empty lists, single-node lists, lists with all identical values, and values at the extremes of the existing range.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

A heap is a ...?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More