Introduction to Rotate List
The Rotate List problem is a classic algorithmic challenge frequently encountered in coding interviews and competitive programming. Given the head of a singly linked list and an integer k, the task is to rotate the list to the right by k places. This means the last k nodes are moved to the front of the list. Understanding how to manipulate linked list pointers efficiently is a foundational skill that translates directly to real-world scenarios such as buffer rotation, circular queues, and memory management in low-level systems.
What Is the Rotate List Problem?
Formally, the problem is defined as follows: you are given the head of a singly linked list and a non-negative integer k. You must rotate the list to the right by k positions. If k is larger than the length of the list, the rotation wraps around because rotating a list of length n by n positions returns the original list.
For example, consider the linked list 1 -> 2 -> 3 -> 4 -> 5 and k = 2. After rotating right by 2, the result becomes 4 -> 5 -> 1 -> 2 -> 3. The last two nodes (4 and 5) are moved to the front, while the remaining nodes shift right.
Edge Cases to Consider
- An empty list (
headisNone). - A list with a single node.
k = 0, which means no rotation is needed.kgreater than the length of the list.kequal to the length of the list (no effective rotation).
Why It Matters
Mastering the Rotate List problem sharpens your ability to reason about pointer manipulation, a critical skill when working with data structures like linked lists, trees, and graphs. In production systems, similar rotation logic appears in:
- Round-robin schedulers: Rotating task queues to distribute work evenly.
- Ring buffers: Cycling through fixed-size buffers in streaming applications.
- Load balancers: Rotating through server pools to distribute traffic.
- Caching systems: Implementing least-recently-used (LRU) eviction policies.
Additionally, this problem is a favorite among interviewers at major tech companies because it tests multiple concepts simultaneously: traversal, cycle detection, modular arithmetic, and careful pointer rewiring.
Step-by-Step Approach
Step 1: Handle Trivial Cases
If the list is empty, contains a single node, or k is zero, return the head immediately. These cases require no rotation and skipping them early prevents unnecessary computation.
Step 2: Compute the Length of the List
Traverse the list from the head to the tail, counting nodes along the way. Keep a reference to the tail node because you will need it later to close the list into a ring.
Step 3: Normalize k
Since rotating by the length of the list produces the same list, compute k = k % length. If the result is zero, no rotation is needed and you can return the head as-is.
Step 4: Find the New Tail
The new tail of the rotated list is located at position length - k - 1 from the head (using zero-based indexing). Traverse from the head until you reach this node. The node immediately after it becomes the new head.
Step 5: Rewire the Pointers
Break the link between the new tail and the new head. Then, connect the old tail to the old head, forming the rotated list. Return the new head.
Complete Python Implementation
Below is the full implementation. First, we define the ListNode class, then the rotateRight function that performs the rotation.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def rotateRight(head, k):
# Step 1: Handle trivial cases
if not head or not head.next or k == 0:
return head
# Step 2: Compute the length and find the tail
length = 1
tail = head
while tail.next:
tail = tail.next
length += 1
# Step 3: Normalize k
k = k % length
if k == 0:
return head
# Step 4: Find the new tail (at position length - k - 1)
new_tail = head
for _ in range(length - k - 1):
new_tail = new_tail.next
# Step 5: Rewire the pointers
new_head = new_tail.next
new_tail.next = None
tail.next = head
return new_head
Helper Functions for Testing
To verify the implementation, we need utility functions to build a linked list from a Python list and to convert a linked list back into a Python list for easy inspection.
def build_list(values):
if not values:
return None
head = ListNode(values[0])
current = head
for val in values[1:]:
current.next = ListNode(val)
current = current.next
return head
def list_to_array(head):
result = []
while head:
result.append(head.val)
head = head.next
return result
# Example usage
head = build_list([1, 2, 3, 4, 5])
rotated = rotateRight(head, 2)
print(list_to_array(rotated)) # Output: [4, 5, 1, 2, 3]
# Edge case: k larger than length
head2 = build_list([1, 2, 3])
rotated2 = rotateRight(head2, 5)
print(list_to_array(rotated2)) # Output: [2, 3, 1]
# Edge case: empty list
print(rotateRight(None, 3)) # Output: None
# Edge case: single node
head3 = build_list([42])
print(list_to_array(rotateRight(head3, 10))) # Output: [42]
Complexity Analysis
The algorithm runs in O(n) time, where n is the number of nodes in the list. We traverse the list twice in the worst case: once to compute the length and locate the tail, and once to find the new tail. The space complexity is O(1) because we only use a constant number of pointers regardless of the input size.
This is optimal because you must inspect every node at least once to determine the length of the list, and you cannot rotate without traversing to the break point.
Best Practices
- Always normalize k first: Using
k % lengthprevents unnecessary full rotations and avoids infinite loops or wasted traversal. - Guard against edge cases early: Check for
Noneheads, single-node lists, and zero rotations before doing any work. - Keep references to key nodes: Retain pointers to the old tail, new tail, and new head to avoid re-traversing the list.
- Break links before creating new ones: Set
new_tail.next = Nonebefore connectingtail.next = headto avoid accidental cycles. - Write thorough tests: Cover empty lists, single nodes,
k = 0,k < length,k = length, andk > length. - Use descriptive variable names: Names like
new_tailandnew_headmake the pointer rewiring logic easy to follow during code review.
Common Pitfalls
One frequent mistake is forgetting to break the link between the new tail and the new head, which creates a cycle in the list. Always set new_tail.next = None after capturing the new head reference. Another common error is skipping the modulo operation, leading to excessive traversal when k is very large. Finally, be careful with off-by-one errors when locating the new tail: the loop should run length - k - 1 times, not length - k.
Alternative Approach: Closing the List into a Ring
An elegant variation connects the tail to the head first, forming a circular list, then breaks the ring at the correct position. This reduces the number of distinct pointer operations and can be easier to reason about.
def rotateRightRing(head, k):
if not head or not head.next or k == 0:
return head
# Compute length and close the ring
length = 1
tail = head
while tail.next:
tail = tail.next
length += 1
tail.next = head # Form a circular list
# Normalize k
k = k % length
# Find the new tail, then break the ring
new_tail = head
for _ in range(length - k - 1):
new_tail = new_tail.next
new_head = new_tail.next
new_tail.next = None
return new_head
This approach produces the same result with identical complexity but structures the logic differently. Some developers find it more intuitive because the list is treated as a ring until the final break.
Conclusion
The Rotate List problem is a deceptively simple exercise that reinforces essential linked list manipulation skills. By carefully handling edge cases, normalizing k with modular arithmetic, and rewiring pointers with precision, you can solve the problem in linear time with constant space. Whether you choose the straightforward two-pass approach or the ring-closing variation, the key is to maintain clear references to the critical nodes and break links in the correct order. Mastering this pattern will strengthen your overall ability to work with pointer-based data structures and prepare you for more advanced challenges involving list reordering, cycle detection, and in-place transformations.