Removing Elements from a Singly Linked List: A Comprehensive Guide

When working with singly linked lists, deleting nodes presents a unique challenge: how do we handle the removal of the head node? Unlike other nodes that can be removed through their predecessor, the head node has no previous node to reference.

Understanding the Challenge

In a singly linked list, removing a node typically involves updating the next pointer of the previous node. However, this approach fails for the head node since there is no preceding element. This leads to two fundamental strategies:

  • Direct manipulation – Handle the head node as a special case
  • Sentinel node approach – Introduce a dummy head node for unified processing

Approach 1: Using a Sentinel Node

By creating a dummy node (sentinel) that points to the original head, we can treat all nodes uniformly. Every node now has a predecessor, eliminating the need for special head-node logic.

class Solution:
    def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
        # Create sentinel node pointing to head
        sentinel = ListNode(0)
        sentinel.next = head
        
        # Traverse using a pointer
        ptr = sentinel
        while ptr.next:
            if ptr.next.val == val:
                ptr.next = ptr.next.next
            else:
                ptr = ptr.next
                
        return sentinel.next

Complexity Analysis: Time complexity is O(n) as we traverse the list once. Space complexity is O(1) since we only use constant extra memory.

Approach 2: Direct Head Node Handling

This method explicitly handles head node removal by advancing the head pointer until it either points to a node with a different value or becomes null.

class Solution:
    def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
        # Remove all leading nodes with target value
        while head and head.val == val:
            head = head.next
            
        # Return if list is empty
        if not head:
            return None
            
        # Process remaining nodes
        current = head
        while current.next:
            if current.next.val == val:
                current.next = current.next.next
            else:
                current = current.next
                
        return head

Approach 3: Recursive Solution

Linked lists naturally exhibit recursive structure. The strategy involves processing the rest of the list first, then determining whether to keep or discard the current head node.

class Solution:
    def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
        # Base case: empty list
        if not head:
            return None
            
        # Recursively process remaining nodes
        head.next = self.removeElements(head.next, val)
        
        # Return next node if current should be removed
        return head.next if head.val == val else head

Complexity Analysis: Time complexity remains O(n). Space complexity increases to O(n) due to the recursive call stack depth.

Tags: Linked List LeetCode algorithm Data Structures Recursion

Posted on Thu, 29 Oct 2026 16:48:35 +0000 by frobak