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 →Deleting a node from a singly linked list requires more than locating that node: you must also reach the node before it, because only the predecessor can change its next pointer. Once you have both, deletion is one link change: make the predecessor skip the middle node and point directly to the node after it.
The middle is defined by position, so two pointers let you discover it without counting first. Move fast by two nodes for every one node slow moves. When fast reaches the end, slow is at the required middle for both odd and even lengths, provided you remember the node slow occupied just before its final move.
| MEASURE | BOUND | WHY |
|---|---|---|
| Time | O(n) | Fast advances through at most two links per loop and slow advances through at most one, so each link is examined only a constant number of times before the end is reached. The final unlink and delete are constant work. |
| Space | O(1) extra | Only three pointer variables are used, regardless of the list shape. The returned list is required output and is excluded from the extra-space bound; the bound stays O(1) for both balanced-looking and completely linear singly linked lists. |
#include <cstddef>
using namespace std;
class Solution {
public:
ListNode* deleteMiddle(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return nullptr;
}
ListNode* slow = head;
ListNode* fast = head;
ListNode* prev = nullptr;
while (fast != nullptr && fast->next != nullptr) {
prev = slow;
slow = slow->next;
fast = fast->next->next;
}
prev->next = slow->next;
delete slow;
return head;
}
};The order inside the loop is important. Save prev before moving slow, otherwise you lose the only pointer to the node whose next link must change. The initial one-node guard is also part of the algorithm rather than a convenience: without it, prev remains null and the deletion line cannot be applied to the head node.
You can first count the nodes, compute middle index n / 2, and then walk to that index while keeping its predecessor. This uses the same O(n) time and O(1) extra space, but it makes two passes instead of letting two speeds determine the position in one pass. It can be easier to reason about when the index itself is useful, but the fast-slow version is usually shorter.
#include <cstddef>
using namespace std;
class Solution {
public:
ListNode* deleteMiddle(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return nullptr;
}
int length = 0;
for (ListNode* node = head; node != nullptr; node = node->next) {
++length;
}
ListNode* prev = nullptr;
ListNode* middle = head;
for (int index = 0; index < length / 2; ++index) {
prev = middle;
middle = middle->next;
}
prev->next = middle->next;
delete middle;
return head;
}
};