How to recognise
a linked list that must be reversed (whole, a part, or in groups of k), merged, split, reordered or cleaned of some nodes, in place; or a cache that moves items to the front, like LRU (a hash map plus a doubly linked list).
How to solve problems like this
- Draw the nodes and arrows before and after. Name every pointer you must keep so nothing gets lost.
- Before you overwrite a .next, save the node it points to.
- Rewire in a fixed order, then move each pointer one step. Check the first node, the last node and an empty list.
- To reverse only a part (positions left..right, or each group of k), stop at the node before it, reverse the part with the same loop, then connect both ends back.
Variant: Dummy head
The same, but put an empty placeholder node before the head. Then the first node is like any other and needs no special case.
- Reach for a dummy whenever the head itself may change: merging, removing nodes from the front, splitting into parts.
- Keep tail on the last node of the answer and only ever append after it: tail.next = node, then tail = tail.next.
- Move the pointer of the list you took from. When one list runs out, attach the rest of the other in one step.
- Return dummy.next. To partition a list or drop duplicate runs, build one or two answers with their own dummies and join them at the end.
Where it's used
- The Linux kernel. The kernel keeps processes, open files and much more in linked lists (struct list_head). Taking a process off a queue means re-linking two links, with nothing moved.
- memcached. Entries sit in a doubly linked list from recently used to long unused. A used entry is moved to the front, and when memory runs out, the tail is dropped.
- Memory allocation. malloc in glibc keeps free chunks of memory in linked lists. Freeing a chunk links it into a list; allocating one unlinks it.