Advanced manipulations
Advanced list manipulations: stable partition around a value, rotation, swapping nodes in pairs, flattening a multi-level list, cloning a list with random pointers and the node management behind an LRU cache.
Learning goals
- Rearrange nodes by relinking, never copying values
- Rotate a list using a temporary ring
- Clone a list with random pointers in O(1) extra pointers or with a map
- Build an O(1) LRU cache from a hash map and a doubly linked list
Prerequisites
Partition list
To partition a list around x so that nodes smaller than x come first while keeping the original order inside each part, build two chains with dummy heads (a “less” chain and a “more” chain), append each node to the right chain, then link the chains and cut the end of the second so it points to null. A single pass, O(n) time, O(1) extra space, and the result is stable.
Cutting the end (more.next = null) is the step people forget; without it the list can form a cycle.
1static Node partition(Node head, int x) {2Node lessHead = new Node(0), moreHead = new Node(0), less = lessHead, more = moreHead;3for (Node cur = head; cur != null; cur = cur.next) {4if (cur.val < x) { less.next = cur; less = cur; }5else { more.next = cur; more = cur; }6}7more.next = null; // cut the old tail link8less.next = moreHead.next; // join9return lessHead.next;10}
Partition, rotate and swap pairs by relinking
Interactive · edit the inputlinked list · 6 nodes
Split into two chains: values < 3 and values >= 3, each in original order, then join them.
1static Node partition(Node head, int x) { // nodes < x first, then the rest; stable2Node lessHead = new Node(0), moreHead = new Node(0), less = lessHead, more = moreHead;3for (Node cur = head; cur != null; cur = cur.next) {4if (cur.val < x) { less.next = cur; less = cur; }5else { more.next = cur; more = cur; }6}7more.next = null; // cut the old tail link8less.next = moreHead.next; // join the two chains9return lessHead.next;10}11static Node rotateRight(Node head, int k) {12if (head == null || head.next == null || k == 0) return head;13int n = 1; Node tail = head; while (tail.next != null) { tail = tail.next; n++; }14tail.next = head; // close the list into a ring15Node newTail = head; for (int i = 0; i < n - k % n - 1; i++) newTail = newTail.next;16Node newHead = newTail.next; newTail.next = null; // cut the ring after the new tail17return newHead;18}19static Node swapPairs(Node head) {20Node dummy = new Node(0); dummy.next = head; Node prev = dummy;21while (prev.next != null && prev.next.next != null) {22Node a = prev.next, b = a.next;23a.next = b.next; b.next = a; prev.next = b; // relink the pair24prev = a;25}26return dummy.next;27}
Live state
n
6
Execution trace (dry run table)Show 9 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | n=6 | Split into two chains: values < 3 and values >= 3, each in original order, then join them. |
| 2 | L4 | cur=1 | 1 < 3: append to the "less" chain. |
| 3 | L5 | cur=4 | 4 >= 3: append to the "more" chain. |
| 4 | L5 | cur=3 | 3 >= 3: append to the "more" chain. |
| 5 | L4 | cur=2 | 2 < 3: append to the "less" chain. |
| 6 | L5 | cur=5 | 5 >= 3: append to the "more" chain. |
| 7 | L4 | cur=2 | 2 < 3: append to the "less" chain. |
| 8 | L7 | more.next = null: the last node of the second chain must not point back into the first. | |
| 9 | L8 | return=[1, 2, 2, 4, 3, 5] | Joined: [1, 2, 2, 4, 3, 5]. Both chains kept their original order, so the partition is stable. O(n) time, O(1) extra space. |
Rotate list
Rotating right by k moves the last k nodes to the front. Walk to the tail while counting the length n, link the tail to the head to form a ring, walk n - k mod n - 1 steps from the head to find the new tail, then cut the ring after it. Reduce k modulo n first, because rotating by n changes nothing. O(n) time, O(1) space.
Try k larger than the length.
1static Node rotateRight(Node head, int k) {2if (head == null || head.next == null || k == 0) return head;3int n = 1; Node tail = head;4while (tail.next != null) { tail = tail.next; n++; }5tail.next = head; // make a ring6Node newTail = head;7for (int i = 0; i < n - k % n - 1; i++) newTail = newTail.next;8Node newHead = newTail.next;9newTail.next = null; // cut the ring10return newHead;11}
Partition, rotate and swap pairs by relinking
Interactive · edit the inputlinked list · 6 nodes
Split into two chains: values < 3 and values >= 3, each in original order, then join them.
1static Node partition(Node head, int x) { // nodes < x first, then the rest; stable2Node lessHead = new Node(0), moreHead = new Node(0), less = lessHead, more = moreHead;3for (Node cur = head; cur != null; cur = cur.next) {4if (cur.val < x) { less.next = cur; less = cur; }5else { more.next = cur; more = cur; }6}7more.next = null; // cut the old tail link8less.next = moreHead.next; // join the two chains9return lessHead.next;10}11static Node rotateRight(Node head, int k) {12if (head == null || head.next == null || k == 0) return head;13int n = 1; Node tail = head; while (tail.next != null) { tail = tail.next; n++; }14tail.next = head; // close the list into a ring15Node newTail = head; for (int i = 0; i < n - k % n - 1; i++) newTail = newTail.next;16Node newHead = newTail.next; newTail.next = null; // cut the ring after the new tail17return newHead;18}19static Node swapPairs(Node head) {20Node dummy = new Node(0); dummy.next = head; Node prev = dummy;21while (prev.next != null && prev.next.next != null) {22Node a = prev.next, b = a.next;23a.next = b.next; b.next = a; prev.next = b; // relink the pair24prev = a;25}26return dummy.next;27}
Live state
n
6
Execution trace (dry run table)Show 9 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | n=6 | Split into two chains: values < 3 and values >= 3, each in original order, then join them. |
| 2 | L4 | cur=1 | 1 < 3: append to the "less" chain. |
| 3 | L5 | cur=4 | 4 >= 3: append to the "more" chain. |
| 4 | L5 | cur=3 | 3 >= 3: append to the "more" chain. |
| 5 | L4 | cur=2 | 2 < 3: append to the "less" chain. |
| 6 | L5 | cur=5 | 5 >= 3: append to the "more" chain. |
| 7 | L4 | cur=2 | 2 < 3: append to the "less" chain. |
| 8 | L7 | more.next = null: the last node of the second chain must not point back into the first. | |
| 9 | L8 | return=[1, 2, 2, 4, 3, 5] | Joined: [1, 2, 2, 4, 3, 5]. Both chains kept their original order, so the partition is stable. O(n) time, O(1) extra space. |
Swap pairs
Swapping adjacent pairs by relinking (not by swapping values, which interviewers often forbid) uses a dummy head and a prev pointer. For each pair a, b: a.next = b.next; b.next = a; prev.next = b; then move prev to a. An unpaired last node stays where it is. O(n) time, O(1) space.
Choose Swap nodes in pairs.
1static Node swapPairs(Node head) {2Node dummy = new Node(0); dummy.next = head;3Node prev = dummy;4while (prev.next != null && prev.next.next != null) {5Node a = prev.next, b = a.next;6a.next = b.next;7b.next = a;8prev.next = b;9prev = a;10}11return dummy.next;12}
Partition, rotate and swap pairs by relinking
Interactive · edit the inputlinked list · 6 nodes
Split into two chains: values < 3 and values >= 3, each in original order, then join them.
1static Node partition(Node head, int x) { // nodes < x first, then the rest; stable2Node lessHead = new Node(0), moreHead = new Node(0), less = lessHead, more = moreHead;3for (Node cur = head; cur != null; cur = cur.next) {4if (cur.val < x) { less.next = cur; less = cur; }5else { more.next = cur; more = cur; }6}7more.next = null; // cut the old tail link8less.next = moreHead.next; // join the two chains9return lessHead.next;10}11static Node rotateRight(Node head, int k) {12if (head == null || head.next == null || k == 0) return head;13int n = 1; Node tail = head; while (tail.next != null) { tail = tail.next; n++; }14tail.next = head; // close the list into a ring15Node newTail = head; for (int i = 0; i < n - k % n - 1; i++) newTail = newTail.next;16Node newHead = newTail.next; newTail.next = null; // cut the ring after the new tail17return newHead;18}19static Node swapPairs(Node head) {20Node dummy = new Node(0); dummy.next = head; Node prev = dummy;21while (prev.next != null && prev.next.next != null) {22Node a = prev.next, b = a.next;23a.next = b.next; b.next = a; prev.next = b; // relink the pair24prev = a;25}26return dummy.next;27}
Live state
n
6
Execution trace (dry run table)Show 9 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | n=6 | Split into two chains: values < 3 and values >= 3, each in original order, then join them. |
| 2 | L4 | cur=1 | 1 < 3: append to the "less" chain. |
| 3 | L5 | cur=4 | 4 >= 3: append to the "more" chain. |
| 4 | L5 | cur=3 | 3 >= 3: append to the "more" chain. |
| 5 | L4 | cur=2 | 2 < 3: append to the "less" chain. |
| 6 | L5 | cur=5 | 5 >= 3: append to the "more" chain. |
| 7 | L4 | cur=2 | 2 < 3: append to the "less" chain. |
| 8 | L7 | more.next = null: the last node of the second chain must not point back into the first. | |
| 9 | L8 | return=[1, 2, 2, 4, 3, 5] | Joined: [1, 2, 2, 4, 3, 5]. Both chains kept their original order, so the partition is stable. O(n) time, O(1) extra space. |
Flatten multi-level list
In a multi-level list each node may also have a child pointer to another list. Flattening puts the child list between the node and its next, recursively. Use a stack for the pending next nodes, or walk the list and splice each child in front of the remaining list. O(n) over all nodes.
The same idea flattens nested iterators and menu trees.
1class MNode { int val; MNode next, child; MNode(int v) { val = v; } }2static MNode flatten(MNode head) {3for (MNode cur = head; cur != null; cur = cur.next) {4if (cur.child == null) continue;5MNode child = cur.child, tail = child;6while (tail.next != null) tail = tail.next; // last node of the child list7tail.next = cur.next; // child list continues into the old next8cur.next = child; cur.child = null;9}10return head;11}
Clone random-pointer list
A list whose nodes also have a random pointer to any node (or null) cannot be cloned by copying nodes in order, because a random target may not exist yet. Two standard solutions: a hash map from original node to copy (O(n) space), or interleave each copy right after its original, set copy.random = original.random.next, and then split the lists apart (O(1) extra space).
The interleaving trick avoids the map by using the list itself as the lookup table.
1class RNode { int val; RNode next, random; RNode(int v) { val = v; } }2static RNode copyRandomList(RNode head) {3Map<RNode, RNode> copy = new HashMap<>();4for (RNode cur = head; cur != null; cur = cur.next) copy.put(cur, new RNode(cur.val));5for (RNode cur = head; cur != null; cur = cur.next) {6copy.get(cur).next = cur.next == null ? null : copy.get(cur.next);7copy.get(cur).random = cur.random == null ? null : copy.get(cur.random);8}9return head == null ? null : copy.get(head);10}
LRU node management
An LRU (least recently used) cache needs O(1) lookup, O(1) “mark as most recent” and O(1) eviction of the oldest. A hash map finds a key's node, and a doubly linked list keeps the keys ordered by recency: on a hit unlink the node and insert it at the front, on a miss insert at the front and evict the tail node if the capacity is exceeded. The doubly linked list makes unlinking an arbitrary node O(1). LinkedHashMap with access order implements exactly this.
Run the access sequence and watch hits move nodes to the front.
1static class LRU<K, V> extends LinkedHashMap<K, V> {2private final int capacity;3LRU(int capacity) { super(16, 0.75f, true); this.capacity = capacity; } // true = access order4@Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > capacity; }5}
LRU cache: hash map + doubly linked list
Interactive · edit the inputmost recent → least recent · 0 nodes
map: key → node
A cache of capacity 3. The list keeps keys from most to least recently used; the hash map finds a key's node in O(1).
1class LRUCache {2private final int capacity;3private final Map<Integer, DNode> map = new HashMap<>(); // key -> node: O(1) lookup4private final DNode head = new DNode(), tail = new DNode(); // head.next is the most recently used5boolean access(int key) {6DNode node = map.get(key);7if (node != null) { unlink(node); addFirst(node); return true; } // hit: move to the front8node = new DNode(key); map.put(key, node); addFirst(node); // miss: insert at the front9if (map.size() > capacity) { DNode lru = tail.prev; unlink(lru); map.remove(lru.key); } // evict the oldest10return false;11}12}
Live state
capacity
3
Execution trace (dry run table)Show 18 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L4 | capacity=3 | A cache of capacity 3. The list keeps keys from most to least recently used; the hash map finds a key's node in O(1). |
| 2 | L8 | access=1 hits=0 misses=1 | Access 1: miss. Insert a new node at the front and record it in the map. |
| 3 | L8 | access=2 hits=0 misses=2 | Access 2: miss. Insert a new node at the front and record it in the map. |
| 4 | L8 | access=3 hits=0 misses=3 | Access 3: miss. Insert a new node at the front and record it in the map. |
| 5 | L7 | access=1 hits=1 misses=3 | Access 1: hit. Unlink its node and re-insert it at the front (O(1) because the node knows both neighbours). |
| 6 | L8 | access=4 hits=1 misses=4 | Access 4: miss. Insert a new node at the front and record it in the map. |
| 7 | L9 | evict=2 | The cache holds 4 > 3 keys: evict the least recently used, 2, from the tail and from the map. |
| 8 | L9 | size=3 | 2 is gone. |
| 9 | L8 | access=2 hits=1 misses=5 | Access 2: miss. Insert a new node at the front and record it in the map. |
| 10 | L9 | evict=3 | The cache holds 4 > 3 keys: evict the least recently used, 3, from the tail and from the map. |
| 11 | L9 | size=3 | 3 is gone. |
| 12 | L8 | access=5 hits=1 misses=6 | Access 5: miss. Insert a new node at the front and record it in the map. |
| 13 | L9 | evict=1 | The cache holds 4 > 3 keys: evict the least recently used, 1, from the tail and from the map. |
| 14 | L9 | size=3 | 1 is gone. |
| 15 | L8 | access=1 hits=1 misses=7 | Access 1: miss. Insert a new node at the front and record it in the map. |
| 16 | L9 | evict=4 | The cache holds 4 > 3 keys: evict the least recently used, 4, from the tail and from the map. |
| 17 | L9 | size=3 | 4 is gone. |
| 18 | L12 | hits=1 misses=7 return=[1, 5, 2] | 1 hit and 7 misses out of 8 accesses. Final order, most recent first: [1, 5, 2]. Every operation is O(1). |
Practice ladder
Now try it yourself
Easy
- Partition 1 → 4 → 3 → 2 → 5 → 2 around 3
- Rotate 1 → 2 → 3 → 4 → 5 right by 2
Medium
- Swap pairs in 1 → 2 → 3 → 4 → 5
- Clone a list with random pointers using a HashMap
Advanced
- Implement an LRU cache with your own doubly linked list and a HashMap
Key takeaways
- Rearrange by relinking: two chains for partition, a ring for rotation, a dummy head for pairs.
- Clone with a map or by interleaving copies.
- LRU = hash map + doubly linked list for O(1) everything.