In this assignment, we will solve a more challenging problem with Linked Lists. Without further ado, let's see the problem description:
// Given the head of a linked list, reverse the list and return it.
// Input: 1 -> 2 -> 3 -> 4 -> null
// Output: 4 -> 3 -> 2 -> 1 -> null
Your first instinct might be to do something similar to what we did in the first problem. Initialize two pointers, prev
and curr
. Set prev
to null
and curr
to head
.
In the first step, you could change the next
pointer of curr
node to point to prev
. However, this leads to a problem. Balloons with values 2
, 3
, and 4
have now flown away like in Lenny Kravitz's song because nothing keeps a reference to them.
Thus, we need to introduce a third variable: nextNode
. It will keep a reference to the curr.next
node, or, with our analogy, we need the help of another cat. It will hold the next balloon of the curr
cat.
Let's start over, but let's do it correctly this time.
head
of the linked list is null
, return head
as there are no nodes in the linked list.
prev
and curr
. Set prev
to null
, and curr
to head
.
curr
becomes null
:
nextNode
variable to reference the curr.next
node.
curr
node so that its next
pointer points to the prev
node. This is our 'reversal' step.
prev
and curr
pointers. The prev
node should now reference curr
, and curr
should reference nextNode
. In previous problems, sliding curr
meant using curr = curr.next
. Now, we must use our nextNode
reference, because we've just altered curr.next
to point backwards.
prev
because once curr
becomes null
, the prev
node will reference the new head node.
Here's a step-by-step walkthrough of reversing a linked list 1 -> 2 -> 3 -> 4
:
We initialize two pointers, prev
and curr
. Set prev
to null
and curr
to the head of the linked list (1
).
We initialize a third pointer nextNode
to reference curr.next
(nextNode = curr.next
).
Next, we change the next
pointer of curr
to reference prev
(curr.next = prev
).
Then, we slide the prev
and curr
pointers. The prev
node should point to curr
, (prev = curr
), and curr
node should point to nextNode
(curr = nextNode
).
At the beginning of step 3, we set the nextNode
variable to point to curr.next
which is the node with the value 3
(nextNode = curr.next
).
Then, we change the next
pointer of curr
to point to prev
(curr.next = prev
).
Finally, we slide prev
to reference curr
(prev = curr
), and curr
to reference nextNode
(curr = nextNode
).
Once again, we set nextNode
to curr.next
(nextNode = curr.next
).
Next, we change the next
pointer of curr
to reference prev
(curr.next = prev
).
Finally, we assign prev
to reference curr
(prev = curr
), and curr
to reference nextNode
(curr = nextNode
).
In the fifth and last step, we set nextNode
variable to reference curr.next
, which is null
.
Next, we change the next pointer of curr
to prev
(curr.next = prev
).
Finally, we slide prev
and curr
so that prev
now references the node with the value 4
, and curr
is null
. Since curr
is null
, we are done with the iteration, and we can return prev
, as it references node 4
, which is the new head of the linked list.
You might have noticed that the head
variable still references the node with the value 1
. This is why we can't return head
in the end, as that would be the same as returning a list 1 -> null
.
The resulting linked list after reversing the original one is now: 4 -> 3 -> 2 -> 1 -> null
.
Let's take a look at the code implementation of the algorithm:
class ListNode {
constructor(val = 0, next = null) {
this.val = val;
this.next = next;
}
}
function reverseLinkedList(head) {
if (!head) {
return head;
}
let prev = null;
let curr = head;
while (curr !== null) {
// Hold onto the next node so that we don't lose our list
let nextNode = curr.next;
// Reverse the direction of `curr`
curr.next = prev;
// Slide pointers
prev = curr;
curr = nextNode;
}
return prev;
}
// Helper function to print the linked list
function printList(head) {
let curr = head;
let result = "";
while (curr !== null) {
result += curr.val + " -> ";
curr = curr.next;
}
result += "null";
return result;
}
// Test case 1
const head1 = new ListNode(1);
head1.next = new ListNode(2);
head1.next.next = new ListNode(3);
head1.next.next.next = new ListNode(4);
console.log("Input: ", printList(head1));
console.log("Output:", printList(reverseLinkedList(head1)));
// Input: 1 -> 2 -> 3 -> 4 -> null
// Output: 4 -> 3 -> 2 -> 1 -> null
In this assignment, we have seen that in some problems, we might need to use three pointers (prev
, curr
, and nextNode
) to be able to solve them efficiently.
Another useful tip when trying to solve problems with linked lists is to draw out where your pointers are and what happens in each step when you change next pointers of nodes. This will significantly aid you in solving problems, as you will have a visual representation of the linked list. It will also help you identify any issues, such as the one we encountered in this problem when we rewired curr.next
without first assigning it to the nextNode
variable. Additionally, visualizing the linked list will provide insights into how to fix such issues.