223 lines
8.9 KiB
YAML
223 lines
8.9 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:
|
|
- two-pointers
|
|
|
|
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
|
|
|
|
|
|
|
|
**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
|
|
|
|
|
|
|
|
**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
|
|
|
|
|
|
|
|
**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
|
|
|
|
|
|
|
|
**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.
|