Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

708. Insert into a Sorted Circular Linked List - Leetcode Solution

Problem Description

Given a sorted circular linked list and an integer insertVal, insert a new node with the value insertVal into the list such that it remains a sorted circular linked list. The list is sorted in non-decreasing order. If the list is empty, you should create a new single-node circular list and return the reference to that node.

The input is a reference to a node in the circular linked list (not necessarily the smallest value node), and the integer insertVal to be inserted. You must insert exactly one new node, and you cannot reuse existing nodes.

Key constraints:

  • There may be duplicates in the list.
  • There is always at least one valid way to insert the new value.
  • Do not reuse or remove any existing nodes.

Thought Process

Before jumping into code, let's consider the nature of a circular and sorted linked list. Unlike a regular linked list, the last node points back to the first node, forming a loop. The list is sorted, but the starting node can be anywhere within the list. Our goal is to find the correct spot to insert insertVal so that the order is preserved.

A brute-force approach would be to traverse the list, collect all values, insert the new value in order, and reconstruct the list. However, this is inefficient and unnecessary. Instead, we can take advantage of the sorted and circular properties:

  • While traversing, look for the correct place to insert: where the current node's value is less than or equal to insertVal and the next node's value is greater than or equal to insertVal.
  • We also need to handle the edge case where insertVal is either smaller than all existing values or larger than all existing values, due to the circular nature.
  • If we traverse the entire list and don't find a suitable spot (e.g., all values are the same), we can insert the new node anywhere.
This approach ensures we only need a single traversal, making it efficient.

Solution Approach

Let's break down the algorithm step by step:

  1. Empty List: If the given head is null, create a new node with insertVal and point its next to itself, forming a single-node circular list.
  2. Traverse the List: Start at head and iterate through the list using two pointers: prev (current node) and curr (next node). Since the list is circular, we need to stop when we've looped back to head.
  3. Find Insertion Point: For each pair (prev, curr), check:
    • If prev.val <= insertVal <= curr.val: insert between prev and curr.
    • If prev.val > curr.val (the "pivot" point between max and min in the circle):
      • If insertVal >= prev.val (new max) or insertVal <= curr.val (new min), insert here.
  4. All Same Values: If we loop through the entire list without finding a valid spot (all values are the same), just insert after any node (e.g., after head).
  5. Insert the Node: Create the new node and adjust pointers so the list remains circular and sorted.

This approach ensures we respect the circular and sorted properties, and always insert exactly one new node.

Example Walkthrough

Let's walk through an example:

Input: Circular list: 3 -> 4 -> 1 -> (back to 3), insertVal = 2

  1. Start at node 3: prev = 3, curr = 4
    • 3 <= 2 <= 4? No.
  2. Move to next: prev = 4, curr = 1
    • 4 <= 2 <= 1? No.
    • 4 > 1 (pivot point): Is 2 >= 4 or 2 <= 1? No to both.
  3. Move to next: prev = 1, curr = 3
    • 1 <= 2 <= 3? Yes! Insert between 1 and 3.
  4. Insert node with value 2 between 1 and 3.

Resulting list: 3 -> 4 -> 1 -> 2 -> (back to 3)

If all values were the same (e.g., 1 -> 1 -> 1 -> (back to 1)), we could insert anywhere.

Time and Space Complexity

Brute-force approach:

  • Collect all values, insert, and reconstruct the list: O(n) time and O(n) space.
Optimized approach (as above):
  • We traverse the list at most once: O(n) time, where n is the number of nodes.
  • We only use a constant number of pointers: O(1) extra space.

The optimized approach is efficient and scales well for large lists.

Summary

To insert a value into a sorted circular linked list, we leverage its sorted and circular properties to find the correct insertion point in a single traversal. By carefully handling edge cases (empty list, all same values, insert at pivot), we ensure the list remains sorted and circular, and we always insert exactly one new node. The solution is elegant due to its constant space usage and single-pass traversal, making it both efficient and robust.

Code Implementation

# Definition for a Node.
class Node:
    def __init__(self, val, next=None):
        self.val = val
        self.next = next

class Solution:
    def insert(self, head: 'Node', insertVal: int) -> 'Node':
        new_node = Node(insertVal)
        if not head:
            new_node.next = new_node
            return new_node

        prev, curr = head, head.next
        inserted = False

        while True:
            # Case 1: insertVal fits between prev and curr
            if prev.val <= insertVal <= curr.val:
                inserted = True
            # Case 2: At the pivot point (max to min)
            elif prev.val > curr.val:
                if insertVal >= prev.val or insertVal <= curr.val:
                    inserted = True

            if inserted:
                prev.next = new_node
                new_node.next = curr
                return head

            prev, curr = curr, curr.next
            # If we have gone full circle
            if prev == head:
                break

        # Case 3: All nodes have same value or no suitable place found
        prev.next = new_node
        new_node.next = curr
        return head
      
/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node() {}
    Node(int _val, Node* _next = nullptr) {
        val = _val;
        next = _next;
    }
};
*/

class Solution {
public:
    Node* insert(Node* head, int insertVal) {
        Node* newNode = new Node(insertVal);
        if (!head) {
            newNode->next = newNode;
            return newNode;
        }

        Node* prev = head;
        Node* curr = head->next;
        bool inserted = false;

        do {
            // Case 1: insertVal fits between prev and curr
            if (prev->val <= insertVal && insertVal <= curr->val) {
                inserted = true;
            }
            // Case 2: At the pivot point (max to min)
            else if (prev->val > curr->val) {
                if (insertVal >= prev->val || insertVal <= curr->val) {
                    inserted = true;
                }
            }

            if (inserted) {
                prev->next = newNode;
                newNode->next = curr;
                return head;
            }

            prev = curr;
            curr = curr->next;
        } while (prev != head);

        // Case 3: All nodes have same value or no suitable place found
        prev->next = newNode;
        newNode->next = curr;
        return head;
    }
};
      
/*
// Definition for a Node.
class Node {
    public int val;
    public Node next;
    public Node() {}
    public Node(int _val) {
        val = _val;
    }
    public Node(int _val, Node _next) {
        val = _val;
        next = _next;
    }
}
*/

class Solution {
    public Node insert(Node head, int insertVal) {
        Node newNode = new Node(insertVal);
        if (head == null) {
            newNode.next = newNode;
            return newNode;
        }

        Node prev = head;
        Node curr = head.next;
        boolean inserted = false;

        do {
            // Case 1: insertVal fits between prev and curr
            if (prev.val <= insertVal && insertVal <= curr.val) {
                inserted = true;
            }
            // Case 2: At the pivot point (max to min)
            else if (prev.val > curr.val) {
                if (insertVal >= prev.val || insertVal <= curr.val) {
                    inserted = true;
                }
            }

            if (inserted) {
                prev.next = newNode;
                newNode.next = curr;
                return head;
            }

            prev = curr;
            curr = curr.next;
        } while (prev != head);

        // Case 3: All nodes have same value or no suitable place found
        prev.next = newNode;
        newNode.next = curr;
        return head;
    }
}
      
/**
 * // Definition for a Node.
 * function Node(val, next) {
 *   this.val = val;
 *   this.next = next;
 * };
 */

/**
 * @param {Node} head
 * @param {number} insertVal
 * @return {Node}
 */
var insert = function(head, insertVal) {
    const newNode = new Node(insertVal);
    if (!head) {
        newNode.next = newNode;
        return newNode;
    }
    let prev = head;
    let curr = head.next;
    let inserted = false;
    do {
        // Case 1: insertVal fits between prev and curr
        if (prev.val <= insertVal && insertVal <= curr.val) {
            inserted = true;
        }
        // Case 2: At the pivot point (max to min)
        else if (prev.val > curr.val) {
            if (insertVal >= prev.val || insertVal <= curr.val) {
                inserted = true;
            }
        }
        if (inserted) {
            prev.next = newNode;
            newNode.next = curr;
            return head;
        }
        prev = curr;
        curr = curr.next;
    } while (prev !== head);
    // Case 3: All nodes have same value or no suitable place found
    prev.next = newNode;
    newNode.next = curr;
    return head;
};