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 →The list must be changed by links, so each complete group of k nodes has to be turned around while its nodes stay in memory. For one group, remember the node before it and the node after it. Reverse the arrows inside the group, then connect the old first node, which is now the group's tail, to the following node.
The final short group is special: it is not a group to reverse at all. Before changing any links, count ahead by k nodes. If fewer than k nodes remain, stop immediately. A dummy node before the original head gives every group a real predecessor, so reconnecting the first reversed group uses exactly the same pointer assignments as every later group.
| MEASURE | BOUND | WHY |
|---|---|---|
| Time | O(n) | The look-ahead scan and the reversal together process each node a constant number of times. Every successful iteration advances past a disjoint group, and the final unsuccessful scan stops after at most k nodes, so no node can be charged to unboundedly many groups. |
| Space | O(1) extra | Only a fixed number of ListNode pointers and the dummy node are used; the returned list is required output and is excluded from extra space. The bound stays O(1) for every input shape, including a list of n nodes and a final incomplete group. |
#include <vector>
using namespace std;
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
ListNode dummy(0);
dummy.next = head;
ListNode* prev = &dummy;
while (prev->next != nullptr) {
ListNode* cur = prev->next;
int count = 0;
while (cur != nullptr && count < k) {
cur = cur->next;
++count;
}
if (count < k) {
break;
}
ListNode* start = prev->next;
ListNode* groupEnd = cur;
ListNode* before = start;
ListNode* node = start->next;
while (node != groupEnd) {
ListNode* next = node->next;
node->next = before;
before = node;
node = next;
}
start->next = groupEnd;
prev->next = before;
prev = start;
}
return dummy.next;
}
};The reversal starts with before = start rather than nullptr because start is the first node in the group and must eventually point toward the preceding part of the list. The loop stops when node reaches groupEnd, so the first node after the group is never modified. Afterward, start has become the tail, while before holds the new head.
A recursive solution can first walk k nodes to verify that a complete group exists. It then reverses those k links, recursively processes the suffix beginning at the old head's next pointer, and connects that processed suffix to the old head. This is a different arrangement of the same in-place operation, not a time optimisation: it is shorter conceptually but uses one call frame per group.
#include <vector>
using namespace std;
class Solution {
ListNode* reverseFirstK(ListNode* head, int k) {
ListNode* previous = nullptr;
ListNode* current = head;
while (k > 0) {
ListNode* next = current->next;
current->next = previous;
previous = current;
current = next;
--k;
}
head->next = current;
return previous;
}
public:
ListNode* reverseKGroup(ListNode* head, int k) {
ListNode* node = head;
int count = 0;
while (node != nullptr && count < k) {
node = node->next;
++count;
}
if (count < k) {
return head;
}
ListNode* newHead = reverseFirstK(head, k);
head->next = reverseKGroup(node, k);
return newHead;
}
};The recursive version still runs in O(n) time and uses O(n / k) call-stack space, which is O(n) in the worst case when k is 1. It buys a direct statement of the repeated group operation, but the iterative version is the safer default for a hard linked-list problem because it keeps the extra space at O(1) and cannot overflow the runtime stack.