19. Remove Nth Node From End of List
On LeetCode ->Reformulated question¶
Remove the node that is n positions from the end of a singly linked list, and return the new head.
Example:
Key trick¶
Use two pointers with a dummy node:
- Move
fastahead bynsteps. - Then move
fastandslowtogether untilfastis at the end. slow.nextis the node to remove.
This gives one pass after setup and handles deleting the head cleanly.
Trap¶
Common mistakes:
- Forgetting a dummy node, which makes removing the head awkward.
- Off-by-one errors in the gap between
fastandslow. - Not handling a one-element list.
- In helpers, crashing on
[]by returningnodes[0].
Why is this question interesting?¶
It tests linked-list pointer control:
- local pointer updates
- edge-case handling
- converting a 2-pass idea into a 1-pass gap trick
Solution with idiomatic Python¶
from typing import Optional
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
# Dummy simplifies deleting the head node.
dummy = ListNode(0, head)
fast = dummy
slow = dummy
# Keep fast n steps ahead.
for _ in range(n):
fast = fast.next
# When fast reaches the end, slow is just before the node to remove.
while fast.next:
fast = fast.next
slow = slow.next
# Delete target node.
slow.next = slow.next.next
return dummy.next
Pytest test¶
import pytest
@pytest.mark.parametrize(
("values", "n", "expected"),
[
([1, 2, 3, 4, 5], 2, [1, 2, 3, 5]),
([1], 1, []),
([1, 2], 1, [1]),
([1, 2], 2, [2]),
([1, 2, 3], 3, [2, 3]),
([1, 2, 3], 1, [1, 2]),
([1, 2, 3, 4], 4, [2, 3, 4]),
([1, 2, 3, 4], 2, [1, 2, 4]),
],
)
def test_remove_nth_from_end(values, n, expected):
head = build_linked_list(values)
got = Solution().removeNthFromEnd(head, n)
assert linked_list_to_list(got) == expected
Comment my solution¶
Your solution works on the shown tests, but it is harder than needed.
Main remarks:
- The standard two-pointer gap solution is shorter and easier to verify.
next = node.nextis unused.- The
node_cur_i - node_i <= n - 1condition is not intuitive, so it is easy to introduce off-by-one bugs. build_linked_listshould handle[]safely if reused elsewhere.
from typing import Optional
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def __repr__(self):
return f"ListNode({self.val})"
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
# We assume 1 <= n <= length of list head
node_cur = head # parcour the list
node = head # node to delete
prev = None
next = node.next
node_cur_i = 0 # index in list of node_cur
node_i = 0 # index in list of node
while node_cur is not None:
if node_cur_i - node_i <= n - 1:
# move node_cur
node_cur = node_cur.next
node_cur_i += 1
else:
# move node
prev = node
node = node.next
node_i += 1
if node == head:
return head.next
else:
prev.next = node.next if node.next is not None else None
return head
## Test
import pytest
def build_linked_list(values):
nodes = [ListNode(v) for v in values]
for a, b in zip(nodes, nodes[1:]):
a.next = b
return nodes[0]
def linked_list_to_list(head):
out = []
cur = head
while cur:
out.append(cur.val)
cur = cur.next
return out
@pytest.mark.parametrize(
("values", "n", "expected"),
[
([1, 2, 3, 4, 5], 2, [1, 2, 3, 5]),
([1], 1, []),
([1, 2], 1, [1]),
],
)
def test_delete_node(values, n, expected):
head = build_linked_list(values)
head_of_removed = Solution().removeNthFromEnd(head, n)
assert linked_list_to_list(head_of_removed) == expected
A safer helper is:
def build_linked_list(values):
dummy = ListNode()
cur = dummy
for v in values:
cur.next = ListNode(v)
cur = cur.next
return dummy.next
Code¶
from typing import Optional
import pytest
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def __repr__(self):
return f"ListNode({self.val})"
def build_linked_list(values):
dummy = ListNode()
cur = dummy
for v in values:
cur.next = ListNode(v)
cur = cur.next
return dummy.next
def linked_list_to_list(head):
out = []
cur = head
while cur:
out.append(cur.val)
cur = cur.next
return out
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
# Dummy simplifies deleting the head node.
dummy = ListNode(0, head)
fast = dummy
slow = dummy
# Keep fast n steps ahead.
for _ in range(n):
fast = fast.next
# When fast reaches the end, slow is just before the node to remove.
while fast.next:
fast = fast.next
slow = slow.next
# Delete target node.
slow.next = slow.next.next
return dummy.next
@pytest.mark.parametrize(
("values", "n", "expected"),
[
([1, 2, 3, 4, 5], 2, [1, 2, 3, 5]),
([1], 1, []),
([1, 2], 1, [1]),
([1, 2], 2, [2]),
([1, 2, 3], 3, [2, 3]),
([1, 2, 3], 1, [1, 2]),
([1, 2, 3, 4], 4, [2, 3, 4]),
([1, 2, 3, 4], 2, [1, 2, 4]),
],
)
def test_remove_nth_from_end(values, n, expected):
head = build_linked_list(values)
got = Solution().removeNthFromEnd(head, n)
assert linked_list_to_list(got) == expected