๐ Linked Lists: The Train of Data
The Big Picture
Imagine a train. Each train car holds something preciousโpassengers, cargo, or treasure. But hereโs the magical part: each car knows only about the next car in line. The conductor in the first car can find any car by following the chain, one by one.
Thatโs a Linked List! Each piece of data (a โnodeโ) points to the next one. Unlike an array (which is like assigned seats in a stadium), a linked list is flexibleโyou can add or remove cars without rebuilding the whole train.
๐ Singly Linked List
What Is It?
A Singly Linked List is the simplest train. Each car (node) has:
- Data โ the treasure inside
- Next pointer โ a sign pointing to the next car
class Node {
constructor(value) {
this.value = value;
this.next = null;
}
}
Building Your First Train
// Create nodes (train cars)
let car1 = new Node(10);
let car2 = new Node(20);
let car3 = new Node(30);
// Connect them
car1.next = car2;
car2.next = car3;
// car3.next stays null (end of train)
Visual:
[10] โ [20] โ [30] โ null
Traversing (Walking Through)
To visit every car, start at the front and follow the next signs:
function printList(head) {
let current = head;
while (current !== null) {
console.log(current.value);
current = current.next;
}
}
Why is this cool? You donโt need to know how many cars exist. Just follow until null.
๐ก๏ธ Sentinel and Dummy Node Pattern
The Problem
Adding or removing the first car is tricky. The head might change! You need special code for edge cases.
The Solution: A Fake Front Car
Imagine putting an empty car at the very frontโa โdummyโ or โsentinel.โ It holds no treasure, but it never moves. Now the real first car is always dummy.next.
function insertAtHead(dummy, value) {
let newNode = new Node(value);
newNode.next = dummy.next;
dummy.next = newNode;
}
Before: [dummy] โ [10] โ [20] โ null
After inserting 5: [dummy] โ [5] โ [10] โ [20] โ null
Why Use It?
- No special cases! Head changes? Doesnโt matterโ
dummy.nextis always the real head. - Cleaner code. One pattern for insert/delete anywhere.
// At the end, return dummy.next as the actual head
return dummy.next;
๐ข๐ Floydโs Cycle Detection
The Mystery
What if someone connected the last car back to an earlier car? The train becomes a loop! If you try to walk through, youโll walk forever.
The Trick: Two Runners
Send two people walking:
- Turtle ๐ข walks 1 step at a time
- Rabbit ๐ runs 2 steps at a time
If thereโs a loop, the rabbit will lap the turtle and theyโll meet! If thereโs no loop, the rabbit reaches the end first.
function hasCycle(head) {
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next; // 1 step
fast = fast.next.next; // 2 steps
if (slow === fast) {
return true; // They met! Loop exists
}
}
return false; // Rabbit reached the end
}
Finding Where the Loop Starts
Once they meet inside the loop, reset one to the head. Move both at 1 step. Where they meet again is the loopโs entrance.
function findCycleStart(head) {
let slow = head, fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) break;
}
if (!fast || !fast.next) return null;
slow = head;
while (slow !== fast) {
slow = slow.next;
fast = fast.next;
}
return slow; // The entrance!
}
๐ฏ Finding the Middle of a Linked List
The Challenge
You donโt know the length. How do you find the middle in one pass?
The Same Two-Runner Trick!
- Turtle moves 1 step
- Rabbit moves 2 steps
When rabbit reaches the end, turtle is at the middle!
function findMiddle(head) {
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
}
return slow; // The middle node!
}
Example:
[1] โ [2] โ [3] โ [4] โ [5] โ null
โ โ
slow fast (moves twice as fast)
Final: slow at [3] (the middle)
For even-length lists, this gives the second middle.
๐ Linked List Reversal
The Mission
Turn [1] โ [2] โ [3] โ null into [3] โ [2] โ [1] โ null
The Strategy: Three Pointers
Think of it like a conga line doing an about-face:
prevโ who was behind me (starts asnull)currโ where I am nownextโ whoโs ahead (save it before I turn around!)
function reverseList(head) {
let prev = null;
let curr = head;
while (curr !== null) {
let next = curr.next; // Save next
curr.next = prev; // Turn around!
prev = curr; // Step forward
curr = next;
}
return prev; // New head
}
Step by step:
Start: null โ (prev) [1] โ [2] โ [3] โ null
โ
curr
Step 1: null โ [1] [2] โ [3] โ null
โ โ
prev curr
Step 2: null โ [1] โ [2] [3] โ null
โ โ
prev curr
Step 3: null โ [1] โ [2] โ [3] null
โ โ
prev curr
Return prev โ [3] โ [2] โ [1]
๐ญ Reverse Linked List II
The Mission
Reverse only part of the listโfrom position left to position right.
Example: [1] โ [2] โ [3] โ [4] โ [5], reverse from position 2 to 4
Result: [1] โ [4] โ [3] โ [2] โ [5]
The Strategy
- Walk to position
left - 1(the node before the reversal zone) - Reverse nodes from
lefttoright - Reconnect the reversed section
function reverseBetween(head, left, right) {
let dummy = new Node(0);
dummy.next = head;
// Step 1: Find the node before reversal
let prev = dummy;
for (let i = 1; i < left; i++) {
prev = prev.next;
}
// Step 2: Reverse the sublist
let curr = prev.next;
for (let i = 0; i < right - left; i++) {
let next = curr.next;
curr.next = next.next;
next.next = prev.next;
prev.next = next;
}
return dummy.next;
}
Visual (left=2, right=4):
Before: [1] โ [2] โ [3] โ [4] โ [5]
โ___________โ
reverse this
After: [1] โ [4] โ [3] โ [2] โ [5]
Why Dummy Node?
If left = 1, we need to change the head itself. The dummy gives us a consistent โbeforeโ position.
๐ช Reverse Nodes in k-Group
The Ultimate Challenge
Reverse the list in groups of k. If there arenโt enough nodes left for a full group, leave them as-is.
Example: [1] โ [2] โ [3] โ [4] โ [5], k=2
Result: [2] โ [1] โ [4] โ [3] โ [5] (5 stays because itโs alone)
The Strategy
- Check if we have k nodes ahead
- If yes, reverse this group
- Connect to the next group (recursively or iteratively)
- Repeat
function reverseKGroup(head, k) {
let dummy = new Node(0);
dummy.next = head;
let groupPrev = dummy;
while (true) {
// Check if k nodes exist
let kth = getKth(groupPrev, k);
if (!kth) break;
let groupNext = kth.next;
// Reverse the group
let prev = kth.next;
let curr = groupPrev.next;
while (curr !== groupNext) {
let next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
// Connect
let tmp = groupPrev.next;
groupPrev.next = kth;
groupPrev = tmp;
}
return dummy.next;
}
function getKth(curr, k) {
while (curr && k > 0) {
curr = curr.next;
k--;
}
return curr;
}
Visual (k=3):
Before: [1] โ [2] โ [3] โ [4] โ [5] โ [6] โ [7]
โ_________โ โ_________โ โ___โ
group 1 group 2 < k (keep)
After: [3] โ [2] โ [1] โ [6] โ [5] โ [4] โ [7]
๐ง The Pattern Summary
| Operation | Key Insight |
|---|---|
| Singly Linked List | Node has value + next pointer |
| Dummy Node | Fake head simplifies edge cases |
| Cycle Detection | Slow + Fast runners (1 & 2 steps) |
| Find Middle | Same two-pointer trick |
| Reverse | Three pointers: prev, curr, next |
| Reverse II | Partial reversal with dummy |
| k-Group Reverse | Count k, reverse, connect, repeat |
๐ Youโve Got This!
Linked Lists seem tricky at first, but they follow simple patterns:
- Pointers are just directions โ like signs on a treasure map
- Two-pointer tricks solve many problems elegantly
- Dummy nodes eliminate messy edge cases
Practice these operations until they feel natural. Soon youโll be reversing trains, detecting loops, and manipulating data like a pro!
graph TD A["Master Linked Lists"] --> B["Understand Nodes"] B --> C["Practice Traversal"] C --> D["Learn Two-Pointer"] D --> E["Conquer Reversal"] E --> F["Solve Any Problem!"]
Remember: Every expert was once a beginner. Keep building those trains! ๐
