DEV Community

Cover image for Don't Underestimate the Two Pointers: Removing the N-th Node from the End of a Linked List
ab
ab

Posted on

Don't Underestimate the Two Pointers: Removing the N-th Node from the End of a Linked List

Today's algorithm is Remove Nth Node From End of List:

Given a linked list, remove the n-th node from the end of list and return its head.

For example, if the linked list were 1 > 2 > 3 > 4 > 5, and n = 2, you should return a list with the second node from the end removed, so 1 > 2 > 3 > 5.

In this post, I'll be discussing my approach to this problem, which is to have two pointers running at the same time. I'll then go over how to code the solution using JavaScript, followed by an example.

Two Pointers to the Rescue

In linked list problems, two pointers are often a great way to approach the algorithm. The idea behind two pointers is that when one reaches the end of a linked list, the other will be at an important point in the list (you can see another example of using two pointers in a linked list in this algorithm).

In this problem, the way to use two pointers is to have them be n steps apart from each other. That way, when the first pointer gets to the end of the linked list, the second pointer will be on the node that is to be removed from the list.

The important thing to remember about singly linked lists is that you can only move in one direction--from the head to the tail. That's why two pointers are so useful: you can keep track of two different points in the list at once.

For this algorithm, I'll create two pointers. Once the first pointer is n steps ahead from the head of the list, the second pointer will start. Then, the pointers will continue incrementing, one node at a time, until the first pointer reaches the end of the list (as in, when its value is null). When that happens, the second pointer will skip over the next node, because that one is n steps from the end of the list.

Coding the Solution

The first thing to do will be to create a new list, which will essentially be a copy of the inputted list, but won't include the node to be removed. Since the algorithm provides a definition for a singly linked list, in this function we can create a new list with new ListNode(0), and set it equal to the head of the inputted list.

function removeNthFromEnd(head, n) {
  let copy = new ListNode(0);
  copy.next = head;
  //...
}
Enter fullscreen mode Exit fullscreen mode

Then, we'll want to create two pointers, firstPointer and secondPointer. We'll initialize them at the start of the list copy.

function removeNthFromEnd(head, n) {
  let copy = new ListNode(0);
  copy.next = head;
  let firstPointer = copy;
  let secondPointer = copy;
  //...
}
Enter fullscreen mode Exit fullscreen mode

Now, we want to keep moving the first pointer through 'copy' until it reaches n + 1. To do this, we could use either a for loop or a while loop--just for fun, we'll use a while loop! So, we can create a counter, set it equal to 0, and until the counter reaches n + 1, we'll move firstPointer on to each next node.

function removeNthFromEnd(head, n) {
  let copy = new ListNode(0);
  copy.next = head;
  let firstPointer = copy;
  let secondPointer = copy;
  let counter = 0;
  while (counter < n + 1) {
     firstPointer = firstPointer.next;
     counter++;
  }
  //...
}
Enter fullscreen mode Exit fullscreen mode

At this point, we'll want to increment both the first pointer and the second pointer, one node at a time, until the first pointer reaches the end of the list. We know firstPointer is at the end of the list when its value is equal to null, so we can create a while loop that keeps going as long as the value is not null.

function removeNthFromEnd(head, n) {
  let copy = new ListNode(0);
  copy.next = head;
  let firstPointer = copy;
  let secondPointer = copy;
  let counter = 0;
  while (counter < n + 1) {
     firstPointer = firstPointer.next;
     counter++;
  }
  while (firstPointer !== null) {
    secondPointer = secondPointer.next;
    firstPointer = firstPointer.next;
  }
  //...
}
Enter fullscreen mode Exit fullscreen mode

When the while loop stops executing, we know the first pointer is at the end of the list, which means the second pointer is on the node that's n from the end, so we should skip over it. To skip over it, we can set secondPointer.next equal to secondPointer.next.next.

Finally, we'll want to return the list copy, and in order to do so we'll return copy.next.

function removeNthFromEnd(head, n) {
  let copy = new ListNode(0);
  copy.next = head;
  let firstPointer = copy;
  let secondPointer = copy;
  let counter = 0;
  while (counter < n + 1) {
     firstPointer = firstPointer.next;
     counter++;
  }
  while (firstPointer !== null) {
    secondPointer = secondPointer.next;
    firstPointer = firstPointer.next;
  }
  secondPointer.next = secondPointer.next.next;
  return copy.next;
}
Enter fullscreen mode Exit fullscreen mode

Going Through an Example

Let's use the same example of the list being 1 > 2 > 3 > 4 > 5, and n = 2. That means that in the end, we'll want to return the list 1 > 2 > 3 > 5.

We'll start with both firstPointer and secondPointer pointing at the node 0. When we start, the counter will be 0, and n+1 is 3, so we'll keep moving the firstPointer onto the next node (without moving the secondPointer) until n = 3. So, after the first time in the while loop, firstPointer is at 1. Then firstPointer is at 2. Then firstPointer is at 3, which is the last time that firstPointer will move without secondPointer.

Four images of the same linked list,  raw `1 > 2 > 3 > 4 > 5` endraw . In the first image, a blue  raw `firstPointer` endraw  and a red  raw `secondPointer` endraw  are both pointing the same space before the list starts. In the second image,  raw `secondPointer` endraw  is in the same spot, but  raw `firstPointer` endraw  is pointing at  raw `1` endraw . In the third image,  raw `secondPointer` endraw  is at the same spot, and  raw `firstPointer` endraw  is at  raw `2` endraw . In the final image,  raw `firstPointer` endraw  is at  raw `3` endraw , and  raw `secondPointer` endraw  is in the same spot.

At this point, counter is no longer less than n + 1, so we'll start moving secondPointer as well as firstPointer, and we'll keep doing this until firstPointer is null. So firstPointer is now on 4 and secondPointer is on 1. Then, firstPointer is on 5 and secondPointer is on 2. Finally, firstPointer is null, and secondPointer is on 3.

Three images of the same linked list as above. In the first image,  raw `firstPointer` endraw  is on  raw `4` endraw  and  raw `secondPointer` endraw  is on  raw `1` endraw . In the second image,  raw `firstPointer` endraw  is on  raw `5` endraw , and  raw `secondPointer` endraw  is on  raw `2` endraw . In the third image  raw `firstPointer` endraw  is on the empty space after the list (meaning null), and  raw `secondPointer` endraw  is on  raw `3` endraw .

Because firstPointer is null, the next node for the secondPointer is the node we're skipping over. That means that after 3, second pointer will go straight to 5.

The same linked list as above, except the node  raw `4` endraw  has been removed. Now, a very long arrow stretches between  raw `3` endraw  and  raw `5` endraw , and  raw `secondPointer` endraw  is pointing at  raw `5` endraw .

What remains is 1 > 2 > 3 > 5, which is the given list with the 2nd node from the end removed.

--

Please let me know if you have any questions or other solutions to this problem!

Top comments (0)