Opening the reading…
Opening the reading…
LINKED LIST › LINKED LIST (PART 1)
Open — the attempt gate is not wired up yet
This editorial is meant to unlock after you have run the problem at least once, with the worked solution behind one further deliberate click. That needs per-learner unlock state nothing stores today, so for now the whole article is open.
Try it yourself first →A singly linked list only lets you move forward, so it cannot jump to the n-th node from the end directly. The useful observation is that two pointers can preserve a distance of n nodes. When the front pointer reaches the end, the back pointer is exactly n nodes behind it, at the node that must be removed.
The link you need to change belongs to the node before the target, not to the target itself. Start both pointers at a dummy node placed before the real head, move the front pointer n + 1 steps, and then move both together until the front pointer is null. The back pointer then stops before the target, even when the target is the original head.
| MEASURE | BOUND | WHY |
|---|---|---|
| Time | O(sz) | The front pointer advances n + 1 times and then both pointers advance together until front reaches null. Each pointer moves only forward, so the total number of link traversals is linear in the sz nodes. |
| Space | O(1) extra | Only three pointer variables and one temporary node pointer are used; the returned list is required output and is excluded. The bound does not degrade for any list shape because a linked list has no recursive call stack or auxiliary structure proportional to its length. |
#include <cstddef>
using namespace std;
struct ListNode {
int val;
ListNode* next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode* next) : val(x), next(next) {}
};
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(0);
dummy->next = head;
ListNode* front = dummy;
ListNode* back = dummy;
for (int i = 0; i <= n; ++i) {
front = front->next;
}
while (front != nullptr) {
front = front->next;
back = back->next;
}
ListNode* toDelete = back->next;
back->next = toDelete->next;
ListNode* newHead = dummy->next;
delete toDelete;
delete dummy;
return newHead;
}
};The loop uses i <= n rather than i < n because front starts on the dummy and must end n + 1 links ahead. That extra link places back before the target. The dummy is what makes n equal to the list length safe: front can move through the original head and finish at null without any special head-removal branch.
A competent alternative first counts the nodes, then converts the request into an index from the front. If the list has sz nodes, the target is at zero-based position sz - n. To unlink it, walk to position sz - n - 1 from the dummy. This is not an asymptotic optimisation: it still uses O(sz) time and O(1) extra space, but some readers find the explicit count easier to debug.
#include <cstddef>
using namespace std;
struct ListNode {
int val;
ListNode* next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode* next) : val(x), next(next) {}
};
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(0);
dummy->next = head;
int size = 0;
for (ListNode* node = head; node != nullptr; node = node->next) {
++size;
}
ListNode* previous = dummy;
for (int i = 0; i < size - n; ++i) {
previous = previous->next;
}
ListNode* toDelete = previous->next;
previous->next = toDelete->next;
ListNode* newHead = dummy->next;
delete toDelete;
delete dummy;
return newHead;
}
};