Linked List - (Theory)

1. Introduction to Linked Lists

2. Types of Linked Lists

  1. Singly Linked List

    • Each node points only to the next node.
    class Node {
        int data;
        Node next;
        Node(int data) { this.data = data; }
    }
    
    • Operations TC/SC:
      • Insertion: O(1) at head, O(n) at tail
      • Deletion: O(1) at head, O(n) at tail
      • Search/Traverse: O(n) time, O(1) space
  2. Doubly Linked List

    • Nodes point to both next and previous nodes.
    class DoublyNode {
        int data;
        DoublyNode next, prev;
        DoublyNode(int data) { this.data = data; }
    }
    
    • Operations TC/SC:
      • Insertion/Deletion at ends: O(1)
      • Traversal: O(n) time, O(1) space
  3. Circular Linked List

    • Singly Circular: Last node points back to head.
    • Doubly Circular: Last ↔ Head bidirectional.
    // Singly Circular: Insertion
    void insertHead(Node head, int data) {
        Node newNode = new Node(data);
        if (head == null) {
            newNode.next = newNode; // Self-loop
            head = newNode;
        } else {
            newNode.next = head.next;
            head.next = newNode;
        }
    }
    
    • Operations TC/SC:
      • Insertion/Deletion at ends: O(1)
      • Traversal: O(n) time, O(1) space

3. Core Operations & Traversals

Common Operations (Singly Linked List)

public class SinglyLinkedList {
    Node head;

    // Insert at head (TC: O(1), SC: O(1))
    void insertHead(int data) {
        Node newNode = new Node(data);
        newNode.next = head;
        head = newNode;
    }

    // Insert at tail (TC: O(n), SC: O(1))
    void insertTail(int data) {
        Node newNode = new Node(data);
        if (head == null) head = newNode;
        else {
            Node curr = head;
            while (curr.next != null) curr = curr.next;
            curr.next = newNode;
        }
    }

    // Delete by value (TC: O(n), SC: O(1))
    void delete(int key) {
        if (head == null) return;
        if (head.data == key) {
            head = head.next;
            return;
        }
        Node curr = head, prev = null;
        while (curr != null && curr.data != key) {
            prev = curr;
            curr = curr.next;
        }
        if (curr == null) return; // Key not found
        prev.next = curr.next;
    }

    // Search (TC: O(n), SC: O(1))
    boolean search(int key) {
        Node curr = head;
        while (curr != null) {
            if (curr.data == key) return true;
            curr = curr.next;
        }
        return false;
    }

    // Traversal (TC: O(n), SC: O(1))
    void traverse() {
        Node curr = head;
        while (curr != null) {
            System.out.print(curr.data + " -> ");
            curr = curr.next;
        }
        System.out.println("null");
    }
}

Doubly Linked List Operations

public class DoublyLinkedList {
    DoublyNode head, tail;

    // Insert at head (TC: O(1), SC: O(1))
    void insertHead(int data) {
        DoublyNode newNode = new DoublyNode(data);
        if (head == null) head = tail = newNode;
        else {
            newNode.next = head;
            head.prev = newNode;
            head = newNode;
        }
    }

    // Delete at tail (TC: O(1), SC: O(1))
    void deleteTail() {
        if (tail == null) return;
        if (head == tail) head = tail = null;
        else {
            tail = tail.prev;
            tail.next = null;
        }
    }
}

4. Key Patterns & Template Code

Pattern 1: Two Pointers (Fast & Slow)

Pattern 2: Reverse Linked List

Pattern 3: Cycle Detection (Floyd’s Algorithm)

Pattern 4: Merge Two Sorted Lists

Pattern 5: Remove Duplicates (Sorted List)


5. Advanced Patterns

  1. Palindrome Check:

    • Steps:
      1. Find middle (slow/fast pointers).
      2. Reverse second half.
      3. Compare both halves.
    • TC: O(n), SC: O(1)
  2. Add Two Numbers:

    Node addTwoNumbers(Node l1, Node l2) {
        Node dummy = new Node(0);
        Node curr = dummy;
        int carry = 0;
        while (l1 != null || l2 != null || carry != 0) {
            int sum = carry;
            if (l1 != null) { sum += l1.data; l1 = l1.next; }
            if (l2 != null) { sum += l2.data; l2 = l2.next; }
            carry = sum / 10;
            curr.next = new Node(sum % 10);
            curr = curr.next;
        }
        return dummy.next;
    }
    
    • TC: O(max(m, n)), SC: O(max(m, n))
  3. Intersection of Two Lists:

    • Approach:
      1. Find lengths of both lists.
      2. Align pointers to same start.
      3. Traverse until nodes match.
    • TC: O(m + n), SC: O(1)

6. Complexity Summary

Operation Singly LL Doubly LL Circular LL
Insertion (Head) O(1) O(1) O(1)
Insertion (Tail) O(n) O(1) O(1)
Deletion (Head) O(1) O(1) O(1)
Deletion (Tail) O(n) O(1) O(n)
Search O(n) O(n) O(n)
Traversal O(n) O(n) O(n)

7. Full Template (Singly Linked List)

class Node {
    int data;
    Node next;
    Node(int data) { this.data = data; }
}

public class LinkedList {
    Node head;
    
    // Insert at head
    void insertHead(int data) {
        Node newNode = new Node(data);
        newNode.next = head;
        head = newNode;
    }
    
    // Insert at tail
    void insertTail(int data) {
        Node newNode = new Node(data);
        if (head == null) head = newNode;
        else {
            Node curr = head;
            while (curr.next != null) curr = curr.next;
            curr.next = newNode;
        }
    }
    
    // Delete by value
    void delete(int key) {
        if (head == null) return;
        if (head.data == key) head = head.next;
        else {
            Node curr = head, prev = null;
            while (curr != null && curr.data != key) {
                prev = curr;
                curr = curr.next;
            }
            if (curr != null) prev.next = curr.next;
        }
    }
    
    // Reverse (Iterative)
    void reverse() {
        Node prev = null, curr = head;
        while (curr != null) {
            Node next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }
        head = prev;
    }
    
    // Detect cycle
    boolean hasCycle() {
        Node slow = head, fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) return true;
        }
        return false;
    }
    
    // Find middle node
    Node findMiddle() {
        Node slow = head, fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }
}

Key Takeaways: