45. Reverse Nodes in K-Group
View on NeetCode
View on LeetCode
Problem
Given the head of a linked list, reverse the nodes of the list k at a time, and return the modified list.
k is a positive integer and is less than or equal to the length of the linked list. If the number of nodes is not a multiple of k then left-out nodes, in the end, should remain as is.
You may not alter the values in the list’s nodes, only nodes themselves may be changed.
Example 1:
Input: head = [1,2,3,4,5], k = 2
Output: [2,1,4,3,5]
Example 2:
Input: head = [1,2,3,4,5], k = 3
Output: [3,2,1,4,5]
Constraints:
- The number of nodes in the list is
n. 1 <= k <= n <= 50000 <= Node.val <= 1000
Follow-up: Can you solve the problem in O(1) extra memory space?
Solution
Approach: Iterative Reversal with Pointer Tracking
The key insight is to process the list in groups of k nodes. For each group, we reverse it and connect it properly to the previous and next groups.
Algorithm:
- Check if there are k nodes remaining
- Reverse the k nodes
- Connect the reversed group to previous and next parts
- Move to the next group
- Repeat until fewer than k nodes remain
Implementation
class Solution:
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
# Check if we have k nodes to reverse
def hasKNodes(node, k):
count = 0
while node and count < k:
node = node.next
count += 1
return count == k
# Reverse k nodes starting from head
def reverseK(head, k):
prev = None
curr = head
for _ in range(k):
next_node = curr.next
curr.next = prev
prev = curr
curr = next_node
return prev, curr # New head, next group head
dummy = ListNode(0, head)
group_prev = dummy
while hasKNodes(group_prev.next, k):
# Save group start
group_start = group_prev.next
# Reverse k nodes
new_head, next_group = reverseK(group_start, k)
# Connect reversed group
group_prev.next = new_head
group_start.next = next_group
# Move to next group
group_prev = group_start
return dummy.next
Alternative (Recursive):
class Solution:
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
# Check if we have k nodes
curr = head
count = 0
while curr and count < k:
curr = curr.next
count += 1
# If we have k nodes, reverse them
if count == k:
# Reverse k nodes
prev = None
curr = head
for _ in range(k):
next_node = curr.next
curr.next = prev
prev = curr
curr = next_node
# head is now the last node of reversed group
# Recursively process remaining nodes
head.next = self.reverseKGroup(curr, k)
return prev # New head of reversed group
# Less than k nodes, return as is
return head
Complexity Analysis
- Time Complexity: O(n), where n is the number of nodes. We visit each node twice: once to check if k nodes exist, once to reverse.
- Space Complexity: O(1) for iterative, O(n/k) for recursive due to call stack.
Key Insights
-
Group Validation: Before reversing each group, we must verify that k nodes exist. If not, we leave them unchanged.
- Pointer Tracking: We need to track:
group_prev: last node of the previous groupgroup_start: first node of current group (becomes last after reversal)new_head: new first node after reversalnext_group: first node of next group
- Connection Pattern: After reversing a group:
group_prev.next = new_head(connect previous group to reversed group’s head)group_start.next = next_group(connect reversed group’s tail to next group)
-
Dummy Node: A dummy node simplifies handling the first group, which becomes the new head of the entire list.
- Recursive Elegance: The recursive approach is cleaner - reverse k nodes, then recursively process the rest. The base case handles when fewer than k nodes remain.