This article is from teamten.
It’s common in technical interviews to ask the candidate to reverse a singly-linked list. This demonstrates the ability to work with pointers, visualize a data structure, and work through the subtleties of a non-trivial algorithm. It’s usually immediately obvious that you can just walk the list and reverse the pointers in place. Nearly every candidate starts with two temporary pointers and eventually finds out that you need three. You need to point to the current node (the one you’re handling), the previous node (so you can point back to it), and the next node (so you can move on to it after you’ve disrupted your next pointer). A good implementation is something like this:
Element *reverse(Element *head) { Element *previous = NULL; while (head != NULL) { // Keep next node since we trash // the next pointer. Element *next = head->next; // Switch the next pointer // to point backwards. head->next = previous; // Move both pointers forward. previous = head; head = next; } return previous; }
Here the head pointer is the current node, and previousand next are the other two required nodes. It’s not easy to come up with this. It took me 20 minutes to convince myself that it was correct, and candidates usually take about that long too. Why are we returning previous? When the loop is done, are we really done fixing up all the pointers? Will we ever dereference a NULL next pointer? Any other special cases to handle?
I had been asking the above interview question for months when I realized that I can take a completely different approach. The two easy operations on a linked list are removing from the head and inserting at the head. So remove from the head of one list and insert into the head of another until the first list is empty. The new list will be in the reverse order of the original.
Element *reverse(Element *old_list) { Element *new_list = NULL; while (old_list != NULL) { // Remove element from old list. Element *element = old_list; old_list = old_list->next; // Insert element in new list. element->next = new_list; new_list = element; } return new_list; }
One candidate was convinced that this didn’t reverse the list in place but rather created a brand new list, and therefore used more memory and was less efficient. I couldn’t talk him out of it.
There are two interesting things about this new algorithm. Firstly, it’s obvious that it’s correct: there are no corner cases to worry about and both two-line operations are familiar to anyone who’s manipulated a linked list. Secondly, it’s pretty much identical to the first version (same number of temporary variables, same assignments in slightly different order). The beauty and clarity of it pleases a part of my brain that rarely gets pleased.