Files
codetutor/backend/data/questions/rotate-array.yaml

248 lines
9.6 KiB
YAML

title: Rotate Array
slug: rotate-array
difficulty: medium
leetcode_id: 189
leetcode_url: https://leetcode.com/problems/rotate-array/
categories:
- arrays
- two-pointers
- math
patterns:
- slug: two-pointers
is_optimal: true
function_signature: "def rotate(nums: list[int], k: int) -> list[int]:"
test_cases:
visible:
- input: { nums: [1, 2, 3, 4, 5, 6, 7], k: 3 }
expected: [5, 6, 7, 1, 2, 3, 4]
- input: { nums: [-1, -100, 3, 99], k: 2 }
expected: [3, 99, -1, -100]
- input: { nums: [1, 2], k: 1 }
expected: [2, 1]
hidden:
- input: { nums: [1, 2, 3], k: 4 }
expected: [3, 1, 2]
- input: { nums: [1], k: 0 }
expected: [1]
- input: { nums: [1, 2, 3, 4, 5], k: 5 }
expected: [1, 2, 3, 4, 5]
- input: { nums: [1, 2, 3, 4, 5, 6], k: 2 }
expected: [5, 6, 1, 2, 3, 4]
- input: { nums: [1, 2], k: 3 }
expected: [2, 1]
- input: { nums: [1], k: 1 }
expected: [1]
description: |
Given an integer array `nums`, rotate the array to the right by `k` steps, where `k` is non-negative.
**Note:** You must modify the array *in-place* with O(1) extra space.
constraints: |
- `1 <= nums.length <= 10^5`
- `-2^31 <= nums[i] <= 2^31 - 1`
- `0 <= k <= 10^5`
examples:
- input: "nums = [1,2,3,4,5,6,7], k = 3"
output: "[5,6,7,1,2,3,4]"
explanation: "Rotate 1 step to the right: [7,1,2,3,4,5,6]. Rotate 2 steps: [6,7,1,2,3,4,5]. Rotate 3 steps: [5,6,7,1,2,3,4]."
- input: "nums = [-1,-100,3,99], k = 2"
output: "[3,99,-1,-100]"
explanation: "Rotate 1 step to the right: [99,-1,-100,3]. Rotate 2 steps: [3,99,-1,-100]."
explanation:
intuition: |
Imagine a deck of cards on a table. Rotating right by `k` means taking the last `k` cards and moving them to the front.
The key insight is that **rotation is really just rearranging two segments** of the array. After rotating right by `k`, the array becomes: `[last k elements] + [first n-k elements]`.
But how do we achieve this rearrangement *in-place* without extra space? Here's the elegant trick: **three reversals**.
Think of it like this: if you reverse the entire array, then reverse the first `k` elements, then reverse the remaining elements, you end up with the rotated result. It's like flipping a string of beads, then adjusting the two halves.
For `[1,2,3,4,5,6,7]` with `k=3`:
- Reverse all: `[7,6,5,4,3,2,1]`
- Reverse first 3: `[5,6,7,4,3,2,1]`
- Reverse last 4: `[5,6,7,1,2,3,4]`
This works because reversing is like "flipping" segments, and the right sequence of flips puts everything in the correct rotated position.
approach: |
We solve this using the **Reverse Three Times** approach:
**Step 1: Handle edge cases**
- If `k` is larger than the array length, rotating by `n` steps returns the array to its original position
- Use `k = k % n` to get the effective rotation amount
- If `k` becomes `0`, no rotation needed
&nbsp;
**Step 2: Reverse the entire array**
- Reverse all elements from index `0` to `n-1`
- This puts the last `k` elements at the front, but in reverse order
- The first `n-k` elements are now at the back, also in reverse order
&nbsp;
**Step 3: Reverse the first k elements**
- Reverse elements from index `0` to `k-1`
- This corrects the order of what will be the first part of the result
&nbsp;
**Step 4: Reverse the remaining elements**
- Reverse elements from index `k` to `n-1`
- This corrects the order of the second part of the result
&nbsp;
**Why it works:** Each element ends up in its correct rotated position after the three reversals. The reverse operation is in-place (using two pointers swapping from ends toward the middle), so we use O(1) extra space.
common_pitfalls:
- title: Forgetting to Handle k > n
description: |
If `k` is larger than the array length, you might go out of bounds or produce incorrect results.
For example, with `nums = [1,2]` and `k = 3`, rotating right by 3 is the same as rotating by 1 (since rotating by 2 returns to original).
Always use `k = k % n` at the start to normalize `k` to a value within bounds.
wrong_approach: "Using k directly without modulo"
correct_approach: "Normalize with k = k % n before processing"
- title: Using Extra Space with Slicing
description: |
A tempting solution is: `nums[:] = nums[-k:] + nums[:-k]`
While this produces the correct result, it creates a new array to hold the concatenated slices, using **O(n) extra space**. The problem explicitly asks for O(1) space.
The reverse approach modifies the array in-place using only a few pointer variables.
wrong_approach: "Array slicing and concatenation"
correct_approach: "In-place reversal using two pointers"
- title: One-by-One Rotation
description: |
Another intuitive approach is to rotate by 1 step, `k` times:
- Store last element
- Shift all elements right by 1
- Place stored element at front
- Repeat `k` times
This is correct but has **O(n * k) time complexity**. With `n = k = 10^5`, that's 10 billion operations — a guaranteed TLE.
The triple-reverse approach is O(n) regardless of `k`.
wrong_approach: "Rotate one step at a time, k times"
correct_approach: "Triple reverse for O(n) time"
key_takeaways:
- "**Reversal trick**: Three reversals can achieve rotation in O(n) time and O(1) space — a classic technique"
- "**Modulo for cycles**: When dealing with rotations or circular operations, `k % n` normalizes the operation"
- "**In-place operations**: Two-pointer swapping from both ends is the standard way to reverse in-place"
- "**Pattern recognition**: This same reversal technique works for rotating strings and other sequence problems"
time_complexity: "O(n). Each element is visited at most twice (once per reversal it participates in)."
space_complexity: "O(1). We only use a few variables for indices and swapping, regardless of input size."
solutions:
- approach_name: Reverse Three Times
is_optimal: true
code: |
def rotate(nums: list[int], k: int) -> None:
"""
Rotate array in-place using triple reversal.
"""
n = len(nums)
# Normalize k to handle cases where k > n
k = k % n
# Helper function to reverse a portion of the array
def reverse(left: int, right: int) -> None:
while left < right:
nums[left], nums[right] = nums[right], nums[left]
left += 1
right -= 1
# Step 1: Reverse the entire array
reverse(0, n - 1)
# Step 2: Reverse the first k elements
reverse(0, k - 1)
# Step 3: Reverse the remaining elements
reverse(k, n - 1)
explanation: |
**Time Complexity:** O(n) — Each element is moved at most twice.
**Space Complexity:** O(1) — Only a few variables for indices.
The triple-reverse technique elegantly achieves rotation without extra arrays. By reversing the whole array, then reversing two subarrays, each element lands in its correct rotated position.
- approach_name: Cyclic Replacements
is_optimal: true
code: |
def rotate(nums: list[int], k: int) -> None:
"""
Rotate using cyclic replacements - each element jumps to its final position.
"""
n = len(nums)
k = k % n
if k == 0:
return
count = 0 # Track how many elements we've moved
start = 0
while count < n:
current = start
prev = nums[start]
# Follow the cycle until we return to start
while True:
next_idx = (current + k) % n
# Swap: place prev at next_idx, save what was there
nums[next_idx], prev = prev, nums[next_idx]
current = next_idx
count += 1
# If we've returned to start, this cycle is complete
if current == start:
break
# Move to next starting position for next cycle
start += 1
explanation: |
**Time Complexity:** O(n) — Each element is moved exactly once.
**Space Complexity:** O(1) — Only tracking indices and one temp value.
This approach directly places each element at its final position by following cycles. We start at index 0, move the element to its destination `(0 + k) % n`, then move whatever was there to its destination, and so on until we return to the start. If we haven't moved all elements, we start a new cycle from the next index.
- approach_name: Extra Array (Not Optimal for Space)
is_optimal: false
code: |
def rotate(nums: list[int], k: int) -> None:
"""
Rotate using an extra array - simple but uses O(n) space.
"""
n = len(nums)
k = k % n
# Create a rotated copy
rotated = [0] * n
for i in range(n):
rotated[(i + k) % n] = nums[i]
# Copy back to original array
for i in range(n):
nums[i] = rotated[i]
explanation: |
**Time Complexity:** O(n) — Two passes through the array.
**Space Complexity:** O(n) — Extra array to store rotated result.
This straightforward approach creates a new array where each element is placed directly at its rotated position. While correct and easy to understand, it uses O(n) extra space, which doesn't meet the follow-up challenge of O(1) space. Useful for understanding the problem before optimizing.