Problem overview
The function rotate receives a vector nums and an integer k. It rotates the vector k times to the right and returns it. One rotation moves every number one position to the right, and the last number wraps around to the front. The vector has between 1 and 30,000 elements, and k is between 1 and the length.
For nums = [1, 2, 3, 4, 5] and k = 2, one rotation gives [5, 1, 2, 3, 4] and a second gives [4, 5, 1, 2, 3], which is the answer. For [1, 2, 3, 4, 5, 6, 7, 8] with k = 3, the answer is [6, 7, 8, 1, 2, 3, 4, 5].
The lesson asks for O(n) time and O(1) extra space. That rules out two easy answers: rotating one step at a time k times, which is O(n * k), and building a second vector, which uses O(n) extra space.
Approach: move each element straight to its final slot
After k rotations to the right, the element at position i ends up at position (i + k) % n, where n is the length. The remainder makes positions that run past the end wrap around to the front. So every element has one known destination.
The difficulty is that moving an element into its destination overwrites the element living there. The standard trick is to carry the displaced element along. Take the element at position 0 and put it where it belongs, (0 + k) % n. Pick up the element that was there, and put it where it belongs. Keep going until the chain returns to position 0. Positions visited this way form a cycle.
A single cycle doesn't always reach every position. With n = 4 and k = 2, the chain visits 0, 2, and back to 0, and never touches 1 or 3. In general, the number of independent cycles is the greatest common divisor of n and k, so when it is more than 1, the algorithm must start a new cycle at the next position when one closes.
The lesson expresses "carry" with swaps. The slot start_index acts as the hand: it holds the element being carried. A swap between start_index and the destination drops the carried element into place and puts the displaced one into the hand.
Here is the trace for [1, 2, 3, 4, 5] with k = 2, showing the vector and index after each swap:
[3, 2, 1, 4, 5] index=2
[5, 2, 1, 4, 3] index=4
[2, 5, 1, 4, 3] index=1
[4, 5, 1, 2, 3] index=3
[4, 5, 1, 2, 3] index=0
In the last row, the destination is position 0 itself, so the swap does nothing: the element in the hand is already where it belongs. The cycle has closed. The five iterations produced the right answer.
Step by step
- Set up the state.
nis the length.indexis where the carried element came from, andstart_indexis the slot that acts as the hand and also marks where the current cycle began. Both areusizeand start at 0. - Loop exactly
ntimes. Each iteration places one element, and a swap with itself counts as an iteration. The underscore discards the loop counter, which isn't needed. - Compute the destination.
next_index = (index + k as usize) % n. The cast convertsktousizeso it can be added to an index. - Swap positions
next_indexandstart_indexwith the vector'sswapmethod. The carried element lands in its destination, and the displaced one moves into the hand. - Move on. Set
index = next_index, since the element now in the hand came from there. - Detect a closed cycle. When
index == start_index, the chain is back at the beginning. Add 1 to bothindexandstart_indexto start a fresh cycle at the next position.
The solution in Rust
impl Solution {
fn rotate(mut nums: Vec<i32>, k: i32) -> Vec<i32> {
let n = nums.len();
let mut index: usize = 0;
let mut start_index: usize = 0;
for _ in 0..n {
let next_index = (index + k as usize) % n;
nums.swap(next_index, start_index);
index = next_index;
if index == start_index {
index += 1;
start_index += 1;
}
}
nums
}
}
The function returns [4, 5, 1, 2, 3] for the first example, [6, 7, 8, 1, 2, 3, 4, 5] for the second, and [3, 4, 1, 2] for [1, 2, 3, 4] with k = 2, a case with two cycles.
The Rust details worth a closer look:
mut numsin the signature. The parameter is owned and mutable, soswapcan change it, and the same vector is returned. No second vector is allocated, which is what keeps the extra space atO(1).swaptakes two indices.nums.swap(a, b)exchanges two elements in place, anda == bis allowed and does nothing. The algorithm relies on that for the last step of each cycle. An index out of bounds panics.k as usize. Theascast never fails. For the positive values the constraints allow, it is exact. A negativei32would wrap to a hugeusize, which is one reason the constraints matter here.- Mixing integer types.
index + kwould be a compile error, becauseusizeandi32don't mix. The cast makes both sidesusize. - The remainder keeps the index in range.
% nyields a value from 0 ton - 1, soswapnever panics. After the last iteration,indexmay equaln, but it is no longer used. - A standard-library shortcut.
nums.rotate_right(k % n)does the same job in one call. The lesson builds the algorithm by hand to practice cycle-following.
Complexity
Time: O(n). The loop runs exactly n times, and each iteration does a remainder, a swap and a comparison, all constant work. Every element is moved once.
Space: O(1). Only n, index, start_index and next_index are stored, however long the vector is.
Edge cases and common mistakes
- One element.
[7]withk = 1gives[7]. The only swap is the element with itself. kequal to the length.[1, 2, 3]withk = 3returns[1, 2, 3]: a full turn changes nothing. Every destination is the element's own position, and each iteration closes a one-element cycle.- Several cycles.
[1, 2, 3, 4, 5, 6]withk = 4has two cycles, since the greatest common divisor of 6 and 4 is 2. The result is[3, 4, 5, 6, 1, 2]. A version that follows only one cycle would leave half of the vector untouched. - Two elements.
[1, 2]withk = 1gives[2, 1]. - Looping until
index == start_indexonly once. That finds one cycle. The loop must runntimes in total, restarting at the next position when a cycle closes. - Rotating the wrong way. A rotation to the right moves the last element to the front. The destination of position
iisi + k, noti - k. - Subtracting from a
usize. Computingindex - kon unsigned numbers underflows. Using+ kand%, as the lesson does, avoids any subtraction.
Testing
nums = [1, 2, 3, 4, 5],k = 2gives[4, 5, 1, 2, 3].nums = [1, 2, 3, 4, 5, 6, 7, 8],k = 3gives[6, 7, 8, 1, 2, 3, 4, 5].nums = [1, 2, 3, 4],k = 2gives[3, 4, 1, 2].nums = [1, 2, 3, 4, 5, 6],k = 4gives[3, 4, 5, 6, 1, 2].nums = [7],k = 1gives[7].nums = [1, 2, 3],k = 3gives[1, 2, 3].
