Mastering Patterns
[!NOTE] This module explores the core principles of Linked List Patterns, deriving solutions from first principles (Fast/Slow pointers) to build world-class, production-ready expertise.
To truly master Linked Lists, you need to recognize patterns. We’ve covered “Two Pointers” (Fast/Slow). Now let’s apply it to variations.
1. Palindrome Linked List
Problem: Given 1 -> 2 -> 2 -> 1, return true. Given 1 -> 2, return false.
Intuition
We want O(1) Space.
- Find the Middle using Fast/Slow pointers.
- Reverse the second half of the list.
- Compare the first half and the reversed second half node by node.
- (Optional) Restore the list (reverse again).
Solutions
public boolean isPalindrome(ListNode head) {
if (head == null) return true;
// 1. Find Middle
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
// 2. Reverse Second Half
ListNode prev = null;
while (slow != null) {
ListNode nextTemp = slow.next;
slow.next = prev;
prev = slow;
slow = nextTemp;
}
// 3. Compare
ListNode left = head;
ListNode right = prev; // Head of reversed half
while (right != null) {
if (left.val != right.val) return false;
left = left.next;
right = right.next;
}
return true;
}
func isPalindrome(head *ListNode) bool {
if head == nil { return true }
// 1. Find Middle
slow, fast := head, head
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
}
// 2. Reverse
var prev *ListNode
for slow != nil {
nextTemp := slow.Next
slow.Next = prev
prev = slow
slow = nextTemp
}
// 3. Compare
left, right := head, prev
for right != nil {
if left.Val != right.Val { return false }
left = left.Next
right = right.Next
}
return true
}
Complexity
| Strategy | Time | Space | Notes |
|---|---|---|---|
| Reverse Half | O(N) | O(1) | Modifies list temporarily. |
| Array Copy | O(N) | O(N) | Easiest to implement. |
2. Intersection of Two Linked Lists
Problem: Find the node where two lists headA and headB intersect.
Intuition: Switch Tracks
Imagine two runners.
- Runner A runs List A. When they reach the end, they jump to List B head.
- Runner B runs List B. When they reach the end, they jump to List A head.
- If they intersect, they will travel
Len(A) + Len(B)distance and meet exactly at the intersection point.
Interactive Analysis
Watch the runners switch tracks. They meet because (A + B) == (B + A).
Visual Representation
Solutions
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) return null;
ListNode a = headA;
ListNode b = headB;
// They will either meet at intersection or at null (end)
while (a != b) {
a = (a == null) ? headB : a.next;
b = (b == null) ? headA : b.next;
}
return a;
}
func getIntersectionNode(headA, headB *ListNode) *ListNode {
if headA == nil || headB == nil { return nil }
a, b := headA, headB
for a != b {
if a == nil {
a = headB
} else {
a = a.Next
}
if b == nil {
b = headA
} else {
b = b.Next
}
}
return a
}
Complexity
| Strategy | Time | Space | Notes |
|---|---|---|---|
| Two Pointers | O(N + M) | O(1) | Switches tracks to equalize length. |
3. Remove Nth Node From End
Problem: Remove the n-th node from the end of the list.
Intuition: The Gap Strategy
- Move a
fastpointernsteps ahead. - Move both
slowandfasttogether untilfasthits the end. slowis now just before the target node.slow.next = slow.next.next.
[!TIP] Use a Dummy Node at the start to handle edge cases (like removing the head itself).
Solutions
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode slow = dummy;
ListNode fast = dummy;
// Create gap of n+1
for (int i = 0; i <= n; i++) {
fast = fast.next;
}
while (fast != null) {
slow = slow.next;
fast = fast.next;
}
// Remove
slow.next = slow.next.next;
return dummy.next;
}
func removeNthFromEnd(head *ListNode, n int) *ListNode {
dummy := &ListNode{Next: head}
slow, fast := dummy, dummy
for i := 0; i <= n; i++ {
fast = fast.Next
}
for fast != nil {
slow = slow.Next
fast = fast.Next
}
slow.Next = slow.Next.Next
return dummy.Next
}
Complexity
| Strategy | Time | Space | Notes |
|---|---|---|---|
| One Pass | O(N) | O(1) | Uses gap of n nodes. |
4. Copy List with Random Pointer
Problem: A list where nodes have next and random pointers. Deep copy it.
Intuition: Interweaving
- Interweave: Insert copy
A'afterA.A -> A' -> B -> B'. - Connect Random:
curr.next.random = curr.random.next. - Separate: Unzip the lists.
Solutions
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) { this.val = val; }
}
public Node copyRandomList(Node head) {
if (head == null) return null;
// 1. Interweave
Node curr = head;
while (curr != null) {
Node copy = new Node(curr.val);
copy.next = curr.next;
curr.next = copy;
curr = copy.next;
}
// 2. Connect Random
curr = head;
while (curr != null) {
if (curr.random != null) {
curr.next.random = curr.random.next; // Copy's random points to Random's copy
}
curr = curr.next.next;
}
// 3. Separate
Node newHead = head.next;
curr = head;
Node copyCurr = newHead;
while (curr != null) {
curr.next = curr.next.next; // Restore original
if (copyCurr.next != null) {
copyCurr.next = copyCurr.next.next; // Connect copies
}
curr = curr.next;
copyCurr = copyCurr.next;
}
return newHead;
}
// Using Hash Map approach for clarity in Go
func copyRandomList(head *Node) *Node {
if head == nil { return nil }
m := make(map[*Node]*Node)
// 1. Copy Nodes
curr := head
for curr != nil {
m[curr] = &Node{Val: curr.Val}
curr = curr.Next
}
// 2. Connect
curr = head
for curr != nil {
m[curr].Next = m[curr.Next]
m[curr].Random = m[curr.Random]
curr = curr.Next
}
return m[head]
}
Complexity
| Strategy | Time | Space | Notes |
|---|---|---|---|
| Interweaving | O(N) | O(1) | Optimal space (excluding output). |
| Hash Map | O(N) | O(N) | Easier to implement. |