Okay, raise your hand if you have ever been personally victimized by these ridiculously convoluted little data structures.
Okay, maybe that’s a bit dramatic but it sure felt like I was being victimized the first few (or 30) times I had to come up with a solution in an impossible amount of time. Let's add to that the pressure of being watched and judged for the inability to follow one's own logic. If you're like me you may have cried a little... a couple of times.
Nevertheless, in the words of my favorite philosopher, Ron Swanson, “Keep your tears in your eyes, where they belong.”
I have written this article for beginners who have trouble visualizing the solution, turning it into pseudocode, and then coding a working solution.
In it, I will cover what a linked list is, why it’s so confusing, and how to skillfully yet artfully arrange nodes and their pointers to solve one of the most common linked list problems I have seen so far: reversing a linked list.
Let's start by taking a look at the following class definition of a linked list:
To make a linked list, you need something that can hold a value and point to other values. We call these instances list-nodes or just nodes.
A linked list is a way of storing values and linking them together like a chain. It can be defined as a class with a head and a tail property which correspond to the starting and ending nodes, but this is not always so.
Technically, just one node is a linked list, even if it’s pointed to a
For example, using the class from above, let’s create a linked list:
linked_list = ListNode(1) print(linked_list) # this is what it would look like printed to the terminal: # <class ListNode object> # to access the data linked_list.val # 1 linked_list.next # None
Drawn out, our linked_list looks like this:
# 1 -> None
Seriously, what is this? A list for ANTS? It needs to be at least... THREE TIMES BIGGER THAN THIS?!
Haha, nope that’s all it takes. You can keep creating nodes and then connect them:
node2 = ListNode(2) linked_list.next = node2 # or simply: node2.next = ListNode(3)
Now, if we draw it out, our entire linked_list looks like this:
# 1 -> 2 -> 3 -> None
linked_list is still just a single node with a
val (1), and a
A lot of the confusion comes from the fact that there is no list, so getting the value of all the nodes is not as easy as writing a good ol’ for loop. Most times all you get is just a reference to a starting node. As long as you have the starting node, you can still move through all the nodes by traversing.
For Loops? Where we’re going we don’t need for loops! Given a starting node, to get to the next node, the node after next, and all the other nodes, what you need to do is traverse.
# start with the given node curr = node # while loop that keeps going as long as current never becomes NONE while curr is not None: # do something with this node's value or pointer # this will be where we do the logic to reverse print(curr.value) curr = curr.next # set the curr pointer to next node
This gem of a while loop is something you will use often in many LL problems, so practice it and know it like the back of your hand!
If you google the algorithm for this you might see something like this:
DO NOT make the mistake I did thinking you can solve this a few times and remember it later. You might find yourself in an interview saying: “FIRST, I made some pointers. Then I simply set this pointer to that pointer’s next and prev to current’s next, or was it prev to next’s next.. Shoot I can’t remember...”.
Really the only thing you need to remember is that to reverse a linked list iteratively you need THREE different pointers.
Then, even if you can’t remember how to reverse exactly, you can just figure it out by moving the pointers around. Practice “figuring it out” without looking at the pseudocode as many times as you can. For me, “figuring it out” means visualizing it.
1.) Figure out your starting point i.e if you have 1 > 2 -> None, the starting values will be
prev = None, curr = 1, and next = None
2.) Draw it out, EVERYTHING. Including:
Nodes with their next pointers (I drew them as circles with black arrows)
All None values
PREV, CURR, and NEXT pointers ( I color-coded mine )
prev, curr, and next values at the start of each loop
while condition at the start of each loop
3.) Figure out what the arrows need to point to in order for the list to be reversed. You should only move the arrows and not the nodes. Before you move around arrows, make sure you can write this step as code. For example, in this case, we know that all colored pointers are where they need to be except the
next one. If we want the red arrow to point to 2, the logic for that would be ‘next = curr.next’.
Since we are not dealing with lists, we can’t just say
next + 1 or
next = 20 or anything like that. We can only “move around", or get to nodes, using the pointers we have.
4.) Before moving on to a new loop update the values (the ones at the bottom of figure 2) for prev, curr, and next values to wherever the corresponding arrow is pointing to.
5.) At the start of a new loop, go through the code you wrote for the first loop, making sure the logic still applies and achieves the desired effect.
Check out my video of how I do this.
Really follow each step and then try drawing it out yourself without any help. It helps to have a whiteboard or a pencil because you will need to erase and redraw arrows. This takes practice, patience, and iterations. Before I recorded this video I erased my steps and started over at least 3 or 4 times. Sometimes your logic works in the first loop but then when you keep going you realize the pseudocode won’t work further down the line. That’s okay, just take your time and take a 5 min break if you feel like you're getting nowhere.
You’ll notice at the end there, the starting node of our reversed list is now accessible by the
prev pointer variable. This is the value we want to return.
class Solution: def reverseList(self, head): prev = None curr = head nxt = None while curr is not None: nxt = curr.next # // This is where actual reversing happens curr.next = prev prev = curr curr = nxt return prev
There is one edge case that might break our code and that is if we pass in a None value, or in other words an empty list, as a parameter. Let's fix that with a conditional.
class Solution: def reverseList(self, head): if head is None: return head prev = None curr = head nxt = None while curr is not None: nxt = curr.next # This is where actual reversing happens curr.next = prev # then we update values to traverse prev = curr curr = nxt return prev
Linked list problems are common interview challenges, they can be very confusing at first, so practice working with linked lists as much as you can.
Do not try to memorize the logic, it may cause confusion, and moreover, it won't always be the same exact challenge. The best thing to do is get comfortable drawing out nodes and making visual aids.
When reversing a list iteratively, you need 3 pointers: PREV, CURR, and NEXT.
If you haven’t watched Mean Girls, you are missing out on an iconic film, and you should watch it! Only after mastering linked list reversals, that is.
I hope you found this article helpful and enjoyed my pop culture movie references! I’m almost all out of clever ways to fit in movie quotes but I’ll leave you with an age-old classic that has absolutely nothing to do with linked lists or Mean Girls. It doesn’t even go here, so it can’t sit with us!
Okay, really, that was the (second to) last one.
Life moves pretty fast. If you don’t stop and look around once in a while, you could miss it.
Ferris Bueller’s Day Off