Problem overview
The method Rotate receives an int[] named nums and an int named k. It rotates the array k times to the right, in place: one rotation moves every item one position to the right, and the last item moves to the front. The method returns nothing, because it changes the caller's array. It must run in O(n) time and use O(1) extra space. The array has between 1 and 30,000 items, and k is between 1 and its length.
With 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]. With [1, 2, 3, 4, 5, 6, 7, 8] and k = 3, the result is [6, 7, 8, 1, 2, 3, 4, 5].
Approach: move each item straight to its final place
Rotating one step at a time k times costs O(n × k), and building a second array to copy into costs O(n) extra space. Both are ruled out. But the final position of every item is known without any intermediate rotations: the item at index i ends up at index (i + k) % n. The remainder operator wraps indices past the end back to the front.
Moving an item to its target would overwrite whatever is there, so that item has to be moved on in turn to its own target, and so on. The indices 0, k, 2k, ... (all modulo n) form a cycle: following the moves eventually returns to where it started. When n and k share a common factor, a single cycle doesn't reach every position. For n = 6 and k = 2 the cycle from index 0 visits only 0, 2 and 4, and a second cycle through 1, 3 and 5 is needed. The number of cycles is the greatest common divisor of n and k, and every position belongs to exactly one cycle.
The lesson handles this with a trick: nums[startIndex] acts as a hand holding the item in transit. Each step swaps the held item into its target slot and picks up the item that was there, which is the next one to move. When the walk returns to startIndex, the cycle is closed and the walk starts again from the next index.
Here is [1, 2, 3, 4, 5] with k = 2. The array after each of the five steps is [3, 2, 1, 4, 5], [5, 2, 1, 4, 3], [2, 5, 1, 4, 3], [4, 5, 1, 2, 3] and, after a final self-swap that closes the cycle, [4, 5, 1, 2, 3] again. Here n and k share no common factor, so there is one cycle.
Step by step
- Declare three
intvariables:nisnums.Length,indexis the position the walk is at, andstartIndexis where the current cycle began. Both positions start at 0. - Loop exactly
ntimes. Every step puts one item in its final place, and there arenitems, sonsteps are enough whatever the cycle structure. - Compute
nextIndexas(index + k) % n. It is the slot the held item belongs in. - Swap
nums[nextIndex]andnums[startIndex]. The held item lands in its final slot, and the item that was there is now held atstartIndex. - Move on: set
index = nextIndex. - Detect a closed cycle. When
index == startIndex, the walk has come back to its start, and the last swap was a swap of a slot with itself that put the held item home. Increment bothstartIndexandindexto begin the next cycle from the neighbouring slot. If there is no next cycle, the loop ends right after.
The solution in C#
class Solution {
public void Rotate(int[] nums, int k) {
int n = nums.Length, index = 0, startIndex = 0;
for (int c = 0; c < n; c++) {
int nextIndex = (index + k) % n;
(nums[nextIndex], nums[startIndex]) = (nums[startIndex], nums[nextIndex]);
index = nextIndex;
if (index == startIndex) {
startIndex++; index++;
}
}
}
}
The C# details worth knowing:
- Tuple assignment as a swap.
(a, b) = (b, a)evaluates the right side first, then assigns, so no temporary variable is needed. It works on array elements likenums[nextIndex]too. When both elements are the same slot, it assigns a value to itself and changes nothing. - Arrays are reference types.
numsis a reference to the caller's array, so the method changes the caller's data and has return typevoid. Assigning a new array tonumsinside the method would not change the caller's array. - One declaration, three variables.
int n = nums.Length, index = 0, startIndex = 0;declares threeints of the same type in one statement. - Remainder and indices.
%on non-negativeintvalues gives the wrapped index. Hereindexandkare never negative, so there is no negative remainder to worry about. - Integer bounds.
index + kis below2nhere, far from theintlimit. Because the remainder is taken, aklarger thannalso works.
Complexity
Time: O(n). The loop runs exactly n times, and each step does a constant amount of work: one remainder, one swap and one comparison.
Space: O(1). The method uses five int variables and swaps within the array. It allocates no second array.
Edge cases and common mistakes
- One item.
[1]withk = 1:nextIndexis 0, the swap changes nothing, andindexequalsstartIndex, so both increase and the loop ends. The array stays[1]. kequals the length. A full turn gives the original array.[1, 2, 3]withk = 3stays[1, 2, 3]. Every cycle has length 1 here, and each step is a self-swap.- Several cycles.
[1, 2, 3, 4, 5, 6]withk = 2gives[5, 6, 1, 2, 3, 4]. Without thestartIndexrestart, the walk would only touch indices 0, 2 and 4. - Rotating left by mistake. The target of index
iis(i + k) % n. Using- krotates the other way and can give a negative remainder. - Looping until
index == startIndex. That stops after the first cycle. The counted loop ofnsteps is what covers all of them. - Allocating a new array. Copying each item to
(i + k) % nin a new array is correct, but it usesO(n)space and breaks the lesson's limit.
Testing
[1, 2, 3, 4, 5],k = 2gives[4, 5, 1, 2, 3].[1, 2, 3, 4, 5, 6, 7, 8],k = 3gives[6, 7, 8, 1, 2, 3, 4, 5].[1, 2, 3, 4, 5, 6],k = 2gives[5, 6, 1, 2, 3, 4].[1, 2, 3, 4],k = 2gives[3, 4, 1, 2].[1],k = 1gives[1].[1, 2, 3],k = 3gives[1, 2, 3].
