- Can you reverse a sublist of length
kin a linked list in-place? - What if the number of nodes left is less than
k? - Think about how to connect the reversed group with the rest of the list.
- How do you reverse a segment of a linked list?
Try to implement a helper function to reverse a segment [start, end].
- How do you process the list in groups of
k?
Iterate through the list, each time checking if there are at least k nodes left. If so, reverse them; otherwise, leave the rest as is.
- How do you connect the reversed groups?
After reversing, make sure the previous group's tail points to the new head, and the new tail points to the next group.
- This is a classic linked list reversal problem, but applied in fixed-size groups.
- The sentinel node pattern is essential to handle head changes.
- The in-place reversal of a sublist is a standard interview pattern.
Recursively reverse the first k nodes, then call the function on the rest of the list. If there are fewer than k nodes left, leave them as is.
fun reverseKGroup(head: ListNode?, k: Int): ListNode? {
if (head == null) return null
var size = 0
var current = head
while (current != null) {
current = current.next
size++
}
// Check if we have enough nodes to reverse
if (size < k) return head
// Start to reverse k nodes
var previous: ListNode? = null
current = head
var i = 0
while (current != null && i < k) {
val next = current.next
current.next = previous
previous = current
current = next
i++
}
// previous: the last node of the current group, it will be the head of the reversed group
val reversedHead = previous
// current: the head of the next group after reversing the current group
val reversedNext = reverseKGroup(current, k)
head?.next = reversedNext
return reversedHead
}
// Or equivalently
fun reverseKGroup(head: ListNode?, k: Int): ListNode? {
if (head == null) return null
var i = 0
var current: ListNode? = head
while (current != null && i < k) {
current = current.next
i++
}
// Not enough k nodes to reverse
if (i < k) return head
// Start to reverse k nodes
// First, recursively process the rest of the list
var previous: ListNode? = reverseKGroup(current, k)
// Now 'previous' holds the head of the reversed remaining part of the list
// 'current' is reset to the head of the current k-group
current = head
i = 0
while (current != null && i < k) {
val next = current.next
current.next = previous
previous = current
current = next
i++
}
return previous
}- Time Complexity:
O(N) - Space Complexity:
O(N/k)for recursion stack (worst caseO(N)ifk=1).
We process the list one group of k nodes at a time, reverse each group in-place, and then reconnect it back into the list. There are several pointers to keep track of:
... -> groupPrev -> groupStart -> ... -> kthNode -> groupNext -> ...
^^^^^^^^) (^^^^^^^^^^^^^^^^^^^^^^^^^^) (^^^^^^^^
previous group current group next groupWe need 4 key pointers in each iteration:
| Pointer | Description |
|---|---|
groupPrev |
Tail of the previous reversed group, it's used to stitch in the new head of current reversed group. Starts at sentinel node. |
groupStart |
Head of the current group to reverse. |
kthNode |
The k-th node in the current group (last node to reverse), it will become the new head after reversal. |
groupNext |
Node after the current group (used to reconnect after reverse) |
The final goals to reverse the current group:
groupPrevshould link tokthNode.groupStartshould link togroupNext.- Each node in the current group should be reversed.
|------------------------------|
| v
... -> groupPrev groupStart -> ... -> kthNode -> groupNext -> ...
| ^
|------------------------------|
// which is equivalent to
... -> groupPrev -> kthNode -> ... -> groupStart -> groupNext -> ...The steps to reverse the current group:
- Step 1.: Find the k-th node in the current group from
groupPrev.
... -> groupPrev -> groupStart -> node2 -> ... -> kthNode -> groupNext -> ...
^^^^^^^- Step 2.: Reverse the current group:
[groupStart, ... kth].
... -> groupPrev -> groupStart -> node2 -> ... -> kthNode -> groupNext -> ...
|-----------------------------|
... -> groupPrev kthNode -> ... -> node2 -> groupStart -> groupNext -> ...- Step 3.: Stitch in the new head of current reversed group. Now
groupPrev.nextisgroupStart, we need to link it tokthNode. and movegroupPrevtogroupStart.
... -> groupPrev -> kthNode -> ... -> node2 -> groupStart -> groupNext -> ...
|
**groupPrev fun reverseKGroup(head: ListNode?, k: Int): ListNode? {
if (head == null) return null
val sentinel = ListNode(-1)
sentinel.next = head
var groupPrev = sentinel
while (true) {
// Get the k-th node, if not enough nodes, break
var kthNode = getKthNode(groupPrev, k) ?: break
// Get the head of the current group
val groupStart = groupPrev.next
// Preserve the head of the next group
val groupNext = kthNode?.next
// Start to reverse the current group
var previous: ListNode? = groupNext
var current: ListNode? = groupStart
while (current != groupNext) {
val next = current?.next
current?.next = previous
previous = current
current = next
}
// Connect the previous group with the reversed current group
groupPrev.next = kthNode
// Update the previous group to the current group
groupPrev = groupStart
}
return sentinel.next
}
// Move k times to get the k-th node
private fun getKthNode(head: ListNode?, k: Int): ListNode? {
var current = head
for (i in 0 until k) {
current = current?.next ?: return null
}
return current
}// Original list
k = 3
s -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7
// Locate the k-th node, and preserve the head of the next group
s -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7
* * *
groupPrev k groupNext
// Start to reverse the current group
s -> 1 <- 2 <- 3 4 -> 5 -> 6 -> 7
| ^
|--------------|
// Connect the previous group with the reversed current group
val next = groupPrev.next // groupPrev = s, next = 1
groupPrev.next = kthNode // s -> 3
groupPrev = next // groupPrev = 1
// The result after the first iteration
s -> 3 -> 2 -> 1 -> 4 -> 5 -> 6 -> 7
// ----------------
// Continue with the next group
s -> 3 -> 2 -> 1 -> 4 -> 5 -> 6 -> 7
* * *
groupPrev k groupNext- Time Complexity:
O(N), whereNis the number of nodes (each node is visited and reversed once). - Space Complexity:
O(1)(in-place, only a few pointers used).
The key ideas are the same as above, we just use a stack to reverse the nodes in each group. For each iteration, we push k nodes into the stack, and then pop them out to reverse the group. We keep track of two pointers:
current: The current node in the list. We move itktimes to push it into the stack and check if there are enoughknodes left.newCurrent: The last node to build the new list after reversing the current group.
fun reverseKGroup(head: ListNode?, k: Int): ListNode? {
if (head == null) return null
val stack = Stack<ListNode>()
val sentinel = ListNode(-1)
sentinel.next = head
var current = head
var newCurrent = sentinel
while (true) {
var i = 0
// Push `k` nodes into the stack
while (i < k && current != null) {
stack.push(current)
current = current.next
i++
}
// Not enough nodes to reverse
if (i < k) {
break
}
// Pop `k` nodes from the stack and build the new list
while (stack.isNotEmpty()) {
val node = stack.pop()
newCurrent.next = node
newCurrent = node
}
/**
* Connect the new list with the next group
* This is required otherwise the last node of the new list will point to
* the last node of the current group, which will cause a cycle.
* For example, list: 1 -> 2
* stack = [1, 2]
* newCurrent = 1 and break the loop, 1 still points to 2.
*/
newCurrent.next = current
}
return sentinel.next
}- Time Complexity:
O(N), whereNis the number of nodes (each node is visited and reversed once). - Space Complexity:
O(k)for the stack.
TODO: The following solution is not correct.
fun reverseKGroup(head: ListNode?, k: Int): ListNode? {
if (head == null) return null
val sentinel = ListNode(-1)
sentinel.next = head
var current = head
var newCurrent = sentinel
while (true) {
var i = 0
var start = current
// Move to tail of current group
while (i < k - 1 && current != null) {
current = current.next
i++
}
if (i < k - 1 || current == null) break
val nextStart = current?.next
// s -> 2 -> 1
val tail = current!!
newCurrent.next = tail
newCurrent = tail
// 1 -> 2
// 2 -> 1
while (start != tail) {
newCurrent.next = start
newCurrent = newCurrent.next!!
start = start?.next
}
// Move to the head of next group
newCurrent.next = nextStart
current = nextStart
}
return sentinel.next
}- List length < k:
The last group should not be reversed.
How to avoid? Always check if there are at leastknodes before reversing. - k = 1:
The list should remain unchanged. - k = list length:
The entire list is reversed once. - List with exactly k nodes:
Should be reversed once.
- Off-by-one errors: When finding the k-th node, make sure to move
ktimes from the current node, notk-1. - Losing references: Always keep track of the next group's head before reversing.
- Not reconnecting the reversed group properly: After reversal, connect the previous group's tail to the new head, and the new tail to the next group.
- 24. Swap Nodes in Pairs —
k=2special case.