- We use two pointers: ahead and behind, both starting at the head of the list.
-
Step 1: Create a gap of
nnodes.
To understand why, imagine removing the 1st node from the end β which is the last node in the list.
To delete it, we need a pointer referring to the node before it, since in a singly linked list we can only remove a node by changing thenextpointer of its preceding node.
Therefore, if we maintain two pointers β ahead and behind β then ahead must start 1 node ahead of behind. That way, when ahead reaches the last node, behind will be exactly at the node before it β which is where we need to be in order to remove the last node.
Generalizing this idea: If we want to remove the k-th node from the end, ahead should start out k nodes ahead of behind. Then, when ahead reaches the end, behind will be positioned immediately before the node we want to delete.
π§© Implementation: Move ahead forwardnsteps to create this gap. If after these moves ahead becomesnull(which happens whennequals the listβs length), it means the node to remove is the head itself β handled as a special case by returninghead.next. -
Step 2: Handle the edge case where
nequals the listβs length.
After moving aheadnsteps, if it becomesnullβ which happens whennequals the listβs length β it means the node to remove is the head itself.
In that case, we simply returnhead.next, effectively skipping the first node and making the second node the new head. -
Step 3: Move both pointers together.
Move both ahead and behind one step at a time until ahead reaches the last node. At that moment, behind is positioned exactly one node before the node that needs to be deleted. -
Step 4: Remove the target node.
Skip over the target node by settingbehind.next = behind.next.next, effectively removing the N-th node from the end of the list. -
Step 5: Return the updated list.
The modified list starts athead, orhead.nextif the first node was removed.
Remove Nth Node from End of List - JS Solution
JSEditorial
Editorial solutions and visualizations are prepared with care, but we do not guarantee 100% accuracy or completeness. Please use your own judgment to verify results. Visucodize is not responsible for decisions made based on this content.
Solution code
const AHEAD = 'ahead', AHEAD_COLOR = 'blue';
const BEHIND = 'behind', BEHIND_COLOR = 'orange';
function visMainFunction(inputHead, n) {
initClassProperties();
explanationAndColorSchemaNotifications();
const head = convertInputToLinkedList(inputHead);
let ahead = head;
let behind = head;
startPartialBatch();
const list = new VisLinkedList(head, { dataPropName: 'val' });
let aheadVM = ahead && list.makeVisManagerForNode(ahead);
let behindVM = behind && list.makeVisManagerForNode(behind);
aheadVM?.addClass(AHEAD);
behindVM?.addClass(BEHIND);
// Step 1: advance AHEAD by n
notification(`
π <b>Step 1: Create a gap of <code>n</code> = ${n} nodes.</b><br>
Imagine we want to remove the <b>1st node from the end</b> β thatβs the <b>last node</b> in the list.<br>
To delete it, we need a pointer referring to the node <b>before</b> it, since in a singly linked list
we can only remove a node by changing the <code>next</code> pointer of its preceding node.<br><br>
Therefore, if we keep two pointers β ${makeColoredSpan('ahead', AHEAD_COLOR)} and ${makeColoredSpan('behind', BEHIND_COLOR)} β
${makeColoredSpan('ahead', AHEAD_COLOR)} must be <b>1 node ahead</b>, so that when it reaches the end,
${makeColoredSpan('behind', BEHIND_COLOR)} will be exactly at the node <b>before</b> it β
which is where we need to be in order to remove the last node.<br><br>
Generalizing this idea:
If we want to remove the <b>k-th node from the end</b>, ${makeColoredSpan('ahead', AHEAD_COLOR)} must start out
<b>k nodes ahead</b> of ${makeColoredSpan('behind', BEHIND_COLOR)}. Then, when ${makeColoredSpan('ahead', AHEAD_COLOR)}
reaches the end, ${makeColoredSpan('behind', BEHIND_COLOR)} will be right before the node to delete.<br><br>
π§© <b>Implementation:</b> Move ${makeColoredSpan('ahead', AHEAD_COLOR)} forward <code>n</code> = ${n} steps to create this gap.<br>
If after these moves ${makeColoredSpan('ahead', AHEAD_COLOR)} becomes <code>null</code> (which happens when <code>n</code> equals the listβs length),
it means the node to remove is the <b>head</b> itself β handled as a special case by returning <code>head.next</code>.
`);
for (let i = 0; i < n; i++) {
aheadVM?.removeClass(AHEAD);
ahead = ahead.next;
aheadVM = ahead && list.makeVisManagerForNode(ahead);
aheadVM?.addClass(AHEAD);
}
endBatch();
// Step 2: if AHEAD is null, delete head
if (ahead === null) {
startPartialBatch();
notification(`
ποΈ ${makeColoredSpan('ahead', AHEAD_COLOR)} reached <code>null</code> after the initial advance β
this means the node to remove is the <b>first node</b> (the current head).<br>
β
We handle this case by simply returning <code>head.next</code>,
effectively skipping the original head and making the second node the new head.
`);
endBatch();
return head.next;
}
// Step 3: move together until AHEAD hits the end
startPartialBatch();
notification(`
πΆ Move both ${makeColoredSpan('ahead', AHEAD_COLOR)} and ${makeColoredSpan('behind', BEHIND_COLOR)} forward <b>together</b> until ${makeColoredSpan('ahead', AHEAD_COLOR)} reaches the last node.<br>
When ${makeColoredSpan('ahead', AHEAD_COLOR)} is at the end, ${makeColoredSpan('behind', BEHIND_COLOR)} will be positioned <b>exactly one node before</b> the one that must be removed β
this lets us perform the deletion by updating <code>behind.next</code>.
`);
while (ahead.next !== null) {
aheadVM?.removeClass(AHEAD);
behindVM?.removeClass(BEHIND);
ahead = ahead.next;
behind = behind.next;
aheadVM = ahead && list.makeVisManagerForNode(ahead);
behindVM = behind && list.makeVisManagerForNode(behind);
aheadVM?.addClass(AHEAD);
behindVM?.addClass(BEHIND);
}
endBatch();
// Step 4: remove the node after BEHIND
startPartialBatch();
notification(`
π ${makeColoredSpan('ahead', AHEAD_COLOR)} reached the last node β meaning weβve found the correct position of ${makeColoredSpan('behind', BEHIND_COLOR)},
whose <code>next</code> points to the node that must be removed (the Nth node from the end).<br>
ποΈ Remove it by updating <code>behind.next = behind.next.next</code>, effectively skipping over that node.
`);
behind.next = behind.next.next;
endBatch();
notification(`
β
<b>Done!</b> Return <code>head</code> β the list is now updated with the target node removed.
`);
return head;
}
// Styling
function initClassProperties() {
setClassProperties(AHEAD, { borderColor: AHEAD_COLOR });
setClassProperties(BEHIND, { backgroundColor: BEHIND_COLOR });
}
// Notification
function explanationAndColorSchemaNotifications() {
explanationNotification();
notification(`
π¨ <b>Color Schema</b><br><br>
<b>${makeColoredSpan('ahead', AHEAD_COLOR)}</b> β pointer advanced <b>n</b> steps ahead to create the gap; when it reaches the end, we know where to delete.<br>
<b>${makeColoredSpan('behind', BEHIND_COLOR)}</b> β trails behind; after the tandem walk, <code>behind.next</code> is the node to remove.
`);
}
// Utility
function convertInputToLinkedList(arr) {
const dummy = new ListNode(0);
let current = dummy;
for (const val of arr) {
current.next = new ListNode(val);
current = current.next;
}
return dummy.next;
}
function ListNode(val, next) {
this.val = val === undefined ? 0 : val;
this.next = next === undefined ? null : next;
}Solution explanation
Solution explanation
Approach
Time Complexity
O(L) β where L is the listβs length.
Each node is visited at most once by the ahead pointer and at most once by the behind pointer.Space Complexity
O(1) β only a few pointer variables are used.Visucodize editorial solution titled JS Solution for LeetCode's Remove Nth Node from End of List coding problem. Includes code and may include explanation. You can animate the code step by step using the provided input by clicking the run button, or fork it locally to update the code and input for custom visualization. View the original problem at https://leetcode.com/problems/remove-nth-node-from-end-of-list.