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:
-
Circular Linked List: The last node points back to the first node, forming a circle with no
null
references in thenext
pointers. -
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 last1
connects back to the first). -
Given Node: You receive a reference to any single node in the list, not necessarily the smallest or first node.
-
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
- Insert
-
Edge Cases:
- If the list is empty (
head
isnull
), create a new single-node circular list containinginsertVal
and return it - Otherwise, return the original
head
node that was given
- If the list is empty (
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.
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:
-
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 insert6
, it clearly goes between5
and7
. -
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 from5
to1
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
andcurr
) 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, whetherinsertVal
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 insert4
, 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 transition4 → 1
is the wrap-around point. We'd insert5
or0
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 EvaluatorExample 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) andcurr = 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)
→ Now3
points to4
- Set
node.next = curr
→ Now4
points to5
- 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
between5
and1
- 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.
A heap is a ...?
Recommended Readings
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!