Skip to content

Coding patternsBlind 75

Remove Nth Node From End of List

Keep two pointers a fixed gap apart; when the front one falls off the end, the back one stands right before the node to remove. Solved two ways, counting first in two passes and two pointers in one pass, both O(L), with a step-through, code in five languages and the Big O worked out from the code.

You are given the head of a singly linked list and a number position_from_end. Remove the node at that position, counting from the end of the list, and return the head. The last node is at position 1.

Example 1

Input: head = [8, 3, 6, 2, 9, 5], position_from_end = 2

Output: [8, 3, 6, 2, 5]

Counting from the end, 5 is 1st and 9 is 2nd. So 9 goes, and 2 now links straight to 5.

8head36292nd from end5∅

Example 2

Input: head = [4], position_from_end = 1

Output: []

The only node is removed, so the list is empty.

Example 3

Input: head = [1, 7], position_from_end = 2

Output: [7]

The 2nd from the end is the head itself. The new head is 7.

  • 1 ≤ L ≤ 30, where L is the number of nodes
  • 0 ≤ Node.val ≤ 100
  • 1 ≤ position_from_end ≤ L. Below, n is position_from_end.
Is position_from_end always valid?
Yes. 1 ≤ position_from_end ≤ L, so the node always exists.
Can the head be the node to remove?
Yes, when n = L. Then the answer starts at the second node.
Do we know the length?
No. We only get the head. Finding the length costs a full pass.
Can it be done in one pass?
Yes. That is the follow-up, and the two-pointer answer does it.

“n-th from the end” is easy to turn into “k-th from the front” once you know the length. To skip the counting pass, keep two pointers a fixed distance apart. When the front one reaches the end, the back one is exactly where we need it. This is the two pointers pattern on a linked list.

This problem has two approaches. Both are O(L), and the second one needs only one pass.

Two pointers O(L), one pass

Start slow and fast on a dummy node in front of the head. Move fast n + 1 nodes ahead. Then move both, one node at a time, until fast falls off the end (becomes null).

The gap between them never changes: it stays n + 1. When fast is null, it is one step past the last node. So slow is n + 1 steps before null, which is the node just before the one to remove. Point slow.next past it.

The invariant: fast is always exactly n + 1 nodes after slow.

Dslow, fast836295∅
  1. dummy = ListNode(0, head)
  2. slow = fast = dummy
  3. for _ in range(position_from_end + 1):
  4. fast = fast.next
  5. while fast:
  6. slow, fast = slow.next, fast.next
  7. slow.next = slow.next.next
  8. return dummy.next

Both pointers start on the dummy node D. First, fast runs 3 nodes ahead.

1 / 10
Solution.javasolution.pySolution.cssolution.gosolution.ts
class Solution {
// Two pointers: keep fast positionFromEnd + 1 nodes ahead of slow. When fast is
// null, slow is right before the node to remove. One pass, O(L) time, O(1) memory.
public ListNode removeNthFromEnd(ListNode head, int positionFromEnd) {
// A dummy in front gives even the head a node before it.
ListNode dummy = new ListNode(0, head);
ListNode slow = dummy, fast = dummy;
for (int i = 0; i <= positionFromEnd; i++) fast = fast.next;
// The gap stays positionFromEnd + 1, so slow stops that many nodes before null.
while (fast != null) {
slow = slow.next;
fast = fast.next;
}
slow.next = slow.next.next;
return dummy.next;
}
}
Time O(L)Space O(1)Here is how we get these from the code, in four steps.
  1. 1 Find the work that repeats

    The first loop moves fast once per run. The second moves both pointers once per run. Each run is 1 step. The loops run one after the other.

    1. dummy = ListNode(0, head)
    2. slow = fast = dummy
    3. for _ in range(position_from_end + 1):
    4. fast = fast.next
    5. while fast:
    6. slow, fast = slow.next, fast.next
    7. slow.next = slow.next.next
    8. return dummy.next
  2. 2 Count the runs on Example 1

    Example 1 has 6 nodes and n = 2. fast first moves 3 nodes ahead. Then both move 4 times, until fast is null.

    3 + 4 = 7 runs.

    loopfast aheadboth move
    runs34
    total
    3 + 4 = 7
  3. 3 Write the count for any n

    fast starts on the dummy and ends on null. That is L + 1 moves in total, split between the two loops:

    (n + 1) + (L − n) = L + 1

    Check it with Example 1: 6 + 1 = 7.

  4. 4 Keep only the part that grows fastest

    Drop the + 1. What is left is L. It is the same Big O as counting first, but done in one pass.

    So the time is O(L).

  5. S Space: count the extra memory the same way

    The code keeps dummy, slow and fast. The space is O(1).

    dummyslowfast

2,000 random lists of 1 to 8 nodes, removing any position from the tail to the head. Count, then walk and the two pointers gave the same list every time.

Largest inputsResult
30 nodes (the most allowed), removing each of the 30 positions in turn, the head included. Both approaches.Under 0.001 s each, and under 0.001 MB of extra memory: one dummy node and two pointers.