61. Rotate List
Problem Description
You are given the head of a singly linked list and an integer k
. Your task is to rotate the list to the right by k
places.
Rotating a linked list to the right by k
places means:
- Take the last
k
nodes from the end of the list - Move them to the beginning of the list
- Maintain the relative order of all nodes
For example, if you have a linked list 1 -> 2 -> 3 -> 4 -> 5
and k = 2
:
- The last 2 nodes are
4 -> 5
- After rotation, the list becomes
4 -> 5 -> 1 -> 2 -> 3
The solution handles several important cases:
- If the list is empty or has only one node, no rotation is possible, so it returns the original head
- Since rotating by the length of the list results in the same list, the algorithm uses
k % n
wheren
is the length of the list to avoid unnecessary rotations - If
k % n = 0
, no rotation is needed
The algorithm uses a two-pointer technique:
- First, it counts the total number of nodes
n
in the list - It calculates the effective rotation amount as
k % n
- A fast pointer moves
k
steps ahead of a slow pointer - Both pointers then move together until the fast pointer reaches the last node
- At this point, the slow pointer is at the node just before the rotation point
- The list is then split and reconnected: the part after the slow pointer becomes the new beginning, and the original beginning connects to where the fast pointer ended
Intuition
The key insight is recognizing that rotating a linked list to the right by k
positions is equivalent to taking the last k
nodes and moving them to the front. But how do we find these last k
nodes efficiently in a singly linked list where we can only traverse forward?
First, we need to handle the edge case where k
might be larger than the list length. If we have 5 nodes and need to rotate by 7, that's the same as rotating by 7 % 5 = 2
. This is because rotating by the list length brings us back to the original configuration.
To find the split point without traversing the list multiple times, we can use the two-pointer technique. If we need to rotate by k
positions, we need to find the (n-k)
th node from the beginning, which will become the last node of our rotated list.
Here's the clever part: instead of calculating n-k
and then traversing to that position, we can use two pointers with a gap of k
between them. We start by moving the fast pointer k
steps ahead. Then, when we move both pointers together until the fast pointer reaches the end, the slow pointer will naturally be at position n-k
- exactly where we need to split the list.
Why does this work? When the fast pointer has traveled n
nodes total (reaching the end), the slow pointer has traveled n-k
nodes, positioning it right before the rotation point. The node after the slow pointer (slow.next
) becomes our new head, and we reconnect the original tail to the original head to complete the rotation.
This approach elegantly solves the problem in just two passes through the list: one to count the nodes and calculate k % n
, and another to find the split point and perform the rotation.
Learn more about Linked List and Two Pointers patterns.
Solution Approach
The implementation uses the fast and slow pointer technique combined with linked list manipulation. Let's walk through the solution step by step:
Step 1: Handle Edge Cases
if head is None or head.next is None:
return head
If the list is empty or has only one node, no rotation is possible, so we return the original head.
Step 2: Count the Number of Nodes
cur, n = head, 0
while cur:
n += 1
cur = cur.next
We traverse the entire list once to count the total number of nodes n
. This is necessary to handle cases where k > n
.
Step 3: Calculate Effective Rotation
k %= n if k == 0: return head
Since rotating by n
positions brings the list back to its original state, we use modulo operation k %= n
to get the effective rotation amount. If k
becomes 0, no rotation is needed.
Step 4: Set Up Two Pointers with Gap
fast = slow = head
for _ in range(k):
fast = fast.next
Initialize both pointers at the head, then advance the fast pointer by k
positions. This creates a gap of k
nodes between the fast and slow pointers.
Step 5: Move Both Pointers Until Fast Reaches the End
while fast.next:
fast, slow = fast.next, slow.next
Move both pointers simultaneously until the fast pointer reaches the last node (when fast.next
is None
). At this point, the slow pointer is at the (n-k)
th node, which is the node just before the rotation point.
Step 6: Perform the Rotation
ans = slow.next # New head is the node after slow
slow.next = None # Break the link to split the list
fast.next = head # Connect the tail to the original head
return ans # Return the new head
The rotation is achieved by:
- Saving the new head (
slow.next
) - Breaking the link after the slow pointer to split the list into two parts
- Connecting the tail of the second part (where fast is) to the original head
- Returning the new head
This approach efficiently rotates the list in O(n)
time with only two passes through the list and uses O(1)
extra space.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with a concrete example: rotating the linked list 1 -> 2 -> 3 -> 4 -> 5
by k = 2
positions to the right.
Initial State:
head -> 1 -> 2 -> 3 -> 4 -> 5 -> None k = 2
Step 1: Handle Edge Cases The list has more than one node, so we continue.
Step 2: Count Nodes Traverse the list to count nodes:
n = 5
Step 3: Calculate Effective Rotation
k = k % n = 2 % 5 = 2
Since k ≠ 0
, we proceed with rotation.
Step 4: Create Gap Between Pointers Initialize both pointers at head, then move fast pointer 2 steps ahead:
Initial: slow -> 1 -> 2 -> 3 -> 4 -> 5
fast -> 1 -> 2 -> 3 -> 4 -> 5
After moving fast by k=2:
slow -> 1 -> 2 -> 3 -> 4 -> 5
fast -> 3 -> 4 -> 5
Step 5: Move Both Pointers Together Move both pointers until fast reaches the last node:
First move: slow -> 2 -> 3 -> 4 -> 5 fast -> 4 -> 5 Second move: slow -> 3 -> 4 -> 5 fast -> 5 -> None
Now fast is at the last node (5), and slow is at node 3.
Step 6: Perform Rotation
1. ans = slow.next = 4 (node 4 becomes new head)
2. slow.next = None (break link: 3 -> None)
3. fast.next = head (connect 5 -> 1)
Final Result:
Before: 1 -> 2 -> 3 -> 4 -> 5 -> None After: 4 -> 5 -> 1 -> 2 -> 3 -> None
The last 2 nodes (4 and 5) have been moved to the front while maintaining their relative order, successfully rotating the list to the right by 2 positions.
Solution Implementation
1# Definition for singly-linked list.
2# class ListNode:
3# def __init__(self, val=0, next=None):
4# self.val = val
5# self.next = next
6
7class Solution:
8 def rotateRight(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
9 # Handle edge cases: empty list or single node
10 if head is None or head.next is None:
11 return head
12
13 # Count the total number of nodes in the linked list
14 current = head
15 list_length = 0
16 while current:
17 list_length += 1
18 current = current.next
19
20 # Optimize k by taking modulo with list length to avoid unnecessary rotations
21 k = k % list_length
22
23 # If k is 0, no rotation needed
24 if k == 0:
25 return head
26
27 # Use two pointers approach to find the rotation point
28 # Move fast pointer k steps ahead
29 fast_pointer = head
30 slow_pointer = head
31 for _ in range(k):
32 fast_pointer = fast_pointer.next
33
34 # Move both pointers until fast reaches the last node
35 while fast_pointer.next:
36 fast_pointer = fast_pointer.next
37 slow_pointer = slow_pointer.next
38
39 # Perform the rotation:
40 # The new head is the node after slow_pointer
41 new_head = slow_pointer.next
42 # Break the link at the rotation point
43 slow_pointer.next = None
44 # Connect the original tail to the original head
45 fast_pointer.next = head
46
47 return new_head
48
1/**
2 * Definition for singly-linked list.
3 * public class ListNode {
4 * int val;
5 * ListNode next;
6 * ListNode() {}
7 * ListNode(int val) { this.val = val; }
8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
9 * }
10 */
11class Solution {
12 public ListNode rotateRight(ListNode head, int k) {
13 // Handle edge cases: empty list or single node
14 if (head == null || head.next == null) {
15 return head;
16 }
17
18 // Calculate the length of the linked list
19 ListNode current = head;
20 int length = 0;
21 while (current != null) {
22 length++;
23 current = current.next;
24 }
25
26 // Optimize k by taking modulo with length to handle k > length cases
27 k = k % length;
28
29 // If k is 0, no rotation needed
30 if (k == 0) {
31 return head;
32 }
33
34 // Use two pointers technique to find the rotation point
35 // Move fast pointer k steps ahead
36 ListNode fastPointer = head;
37 ListNode slowPointer = head;
38
39 for (int i = 0; i < k; i++) {
40 fastPointer = fastPointer.next;
41 }
42
43 // Move both pointers until fast reaches the last node
44 // Slow will point to the node before the new head
45 while (fastPointer.next != null) {
46 fastPointer = fastPointer.next;
47 slowPointer = slowPointer.next;
48 }
49
50 // Perform the rotation:
51 // 1. New head is the node after slowPointer
52 ListNode newHead = slowPointer.next;
53
54 // 2. Break the link at the rotation point
55 slowPointer.next = null;
56
57 // 3. Connect the last node to the original head
58 fastPointer.next = head;
59
60 return newHead;
61 }
62}
63
1/**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 * int val;
5 * ListNode *next;
6 * ListNode() : val(0), next(nullptr) {}
7 * ListNode(int x) : val(x), next(nullptr) {}
8 * ListNode(int x, ListNode *next) : val(x), next(next) {}
9 * };
10 */
11class Solution {
12public:
13 ListNode* rotateRight(ListNode* head, int k) {
14 // Handle edge cases: empty list or single node
15 if (!head || !head->next) {
16 return head;
17 }
18
19 // Calculate the length of the linked list
20 ListNode* current = head;
21 int length = 0;
22 while (current) {
23 ++length;
24 current = current->next;
25 }
26
27 // Optimize k by taking modulo with length to avoid unnecessary rotations
28 k %= length;
29
30 // If k is 0, no rotation needed
31 if (k == 0) {
32 return head;
33 }
34
35 // Use two-pointer technique to find the rotation point
36 // Fast pointer moves k steps ahead
37 ListNode* fastPointer = head;
38 ListNode* slowPointer = head;
39
40 // Move fast pointer k steps ahead
41 while (k--) {
42 fastPointer = fastPointer->next;
43 }
44
45 // Move both pointers until fast reaches the last node
46 // Slow will point to the node before the new head
47 while (fastPointer->next) {
48 fastPointer = fastPointer->next;
49 slowPointer = slowPointer->next;
50 }
51
52 // Perform the rotation:
53 // 1. New head is the node after slow pointer
54 ListNode* newHead = slowPointer->next;
55
56 // 2. Break the link at rotation point
57 slowPointer->next = nullptr;
58
59 // 3. Connect the last node to the original head
60 fastPointer->next = head;
61
62 return newHead;
63 }
64};
65
1/**
2 * Definition for singly-linked list.
3 * class ListNode {
4 * val: number
5 * next: ListNode | null
6 * constructor(val?: number, next?: ListNode | null) {
7 * this.val = (val===undefined ? 0 : val)
8 * this.next = (next===undefined ? null : next)
9 * }
10 * }
11 */
12
13/**
14 * Rotates a linked list to the right by k positions.
15 * @param head - The head of the linked list
16 * @param k - The number of positions to rotate right
17 * @returns The new head of the rotated linked list
18 */
19function rotateRight(head: ListNode | null, k: number): ListNode | null {
20 // Handle edge cases: empty list or single node
21 if (!head || !head.next) {
22 return head;
23 }
24
25 // First pass: calculate the length of the linked list
26 let current: ListNode | null = head;
27 let listLength: number = 0;
28
29 while (current) {
30 current = current.next;
31 listLength++;
32 }
33
34 // Optimize k by taking modulo with list length to avoid unnecessary rotations
35 k = k % listLength;
36
37 // If k is 0, no rotation needed
38 if (k === 0) {
39 return head;
40 }
41
42 // Use two pointers technique to find the rotation point
43 let fastPointer: ListNode | null = head;
44 let slowPointer: ListNode | null = head;
45
46 // Move fast pointer k steps ahead
47 while (k > 0) {
48 fastPointer = fastPointer!.next;
49 k--;
50 }
51
52 // Move both pointers until fast pointer reaches the last node
53 while (fastPointer!.next) {
54 fastPointer = fastPointer!.next;
55 slowPointer = slowPointer!.next;
56 }
57
58 // Perform the rotation:
59 // The new head is the node after slowPointer
60 const newHead: ListNode | null = slowPointer!.next;
61 // Break the link at the rotation point
62 slowPointer!.next = null;
63 // Connect the end of the list to the original head
64 fastPointer!.next = head;
65
66 return newHead;
67}
68
Time and Space Complexity
Time Complexity: O(n)
, where n
is the number of nodes in the linked list.
The algorithm performs the following operations:
- First traversal to count the total number of nodes:
O(n)
- Calculate
k % n
:O(1)
- Move the fast pointer
k
steps ahead:O(k)
, wherek < n
after the modulo operation - Move both fast and slow pointers until fast reaches the last node:
O(n - k)
Total time: O(n) + O(1) + O(k) + O(n - k) = O(n)
Space Complexity: O(1)
The algorithm only uses a constant amount of extra space:
- A few pointer variables (
cur
,fast
,slow
,ans
) - An integer variable
n
to store the count - No additional data structures are created
All operations are performed in-place by manipulating the pointers of the existing linked list nodes, resulting in constant space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Not Handling k Greater Than List Length
One of the most common mistakes is forgetting to handle cases where k
is greater than the length of the linked list. Without the modulo operation, the fast pointer would try to move beyond the list boundaries, causing a null pointer exception.
Incorrect approach:
# This will crash if k > list_length
fast = head
for _ in range(k): # If k > n, fast will become None
fast = fast.next # NullPointerException when fast is None
Solution:
Always calculate k % list_length
before using k to move pointers. This ensures k is within valid bounds and avoids unnecessary full rotations.
Pitfall 2: Off-by-One Error in Pointer Movement
A frequent error occurs when developers move the fast pointer to the wrong position, often moving it k+1
or k-1
steps instead of exactly k
steps, resulting in incorrect rotation.
Incorrect approach:
# Moving fast pointer to wrong position
while fast.next and k > 0: # This might not move exactly k steps
fast = fast.next
k -= 1
Solution:
Use a precise loop to move the fast pointer exactly k
steps:
for _ in range(k):
fast = fast.next
Pitfall 3: Forgetting to Break the Original Link
When performing the rotation, forgetting to set slow.next = None
creates a cycle in the linked list instead of properly rotating it.
Incorrect approach:
new_head = slow.next
# Forgot: slow.next = None
fast.next = head # This creates a cycle!
return new_head
Solution: Always remember to break the link at the rotation point before reconnecting:
new_head = slow.next
slow.next = None # Critical: break the link to avoid cycle
fast.next = head
Pitfall 4: Not Checking for k = 0 After Modulo
After calculating k % list_length
, if k becomes 0, the list doesn't need rotation. Continuing with the algorithm unnecessarily complicates the logic and may lead to errors.
Incorrect approach:
k = k % list_length # Not checking if k == 0, continuing with unnecessary operations fast = slow = head # ... rest of the code
Solution: Always check if k equals 0 after the modulo operation and return early:
k = k % list_length if k == 0: return head
Pitfall 5: Incorrect Traversal to Count List Length
Using the wrong variable or condition when counting nodes can lead to an incorrect count, affecting the modulo calculation.
Incorrect approach:
n = 1 # Starting with 1 instead of 0
cur = head
while cur.next: # This misses counting the last node
n += 1
cur = cur.next
Solution: Start counting from 0 and traverse until the current node is None:
n = 0
cur = head
while cur:
n += 1
cur = cur.next
Which of the following problems can be solved with backtracking (select multiple)
Recommended Readings
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!