189. Rotate Array - Explanation

Problem Link

Description

You are given an integer array nums, rotate the array to the right by k steps, where k is non-negative.

Example 1:

Input: nums = [1,2,3,4,5,6,7,8], k = 4

Output: [5,6,7,8,1,2,3,4]

Explanation:
rotate 1 steps to the right: [8,1,2,3,4,5,6,7]
rotate 2 steps to the right: [7,8,1,2,3,4,5,6]
rotate 3 steps to the right: [6,7,8,1,2,3,4,5]
rotate 4 steps to the right: [5,6,7,8,1,2,3,4]

Example 2:

Input: nums = [1000,2,4,-3], k = 2

Output: [4,-3,1000,2]

Explanation:
rotate 1 steps to the right: [-3,1000,2,4]
rotate 2 steps to the right: [4,-3,1000,2]

Constraints:

  • 1 <= nums.length <= 100,000
  • -(2^31) <= nums[i] <= ((2^31)-1)
  • 0 <= k <= 100,000

Follow up: Could you do it in-place with O(1) extra space?



Topics

Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Array Manipulation - Understanding how to modify arrays in-place by swapping and shifting elements
  • Modular Arithmetic - Using the modulo operator to handle rotation amounts larger than array length
  • Array Reversal - The technique of reversing a subarray in-place using two pointers
  • Cyclic Traversal - Following cycles in an array where each element points to its destination position

1. Brute Force

Intuition

The simplest way to rotate an array by k positions is to perform k single rotations. In each rotation, we save the last element, shift every element one position to the right, and place the saved element at the front. This mimics the physical act of rotating items in a line. While straightforward, this approach is slow because we repeat the entire shift process k times.

Algorithm

  1. Compute k = k % n to handle cases where k is larger than the array length.
  2. Repeat k times:
    • Store the last element in a temporary variable.
    • Shift all elements one position to the right.
    • Place the saved element at index 0.
class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        n = len(nums)
        k %= n
        while k:
            tmp = nums[n - 1]
            for i in range(n - 1, 0, -1):
                nums[i] = nums[i - 1]
            nums[0] = tmp
            k -= 1

Time & Space Complexity

  • Time complexity: O(nk)O(n * k)
  • Space complexity: O(1)O(1) extra space.

2. Extra Space

Intuition

Instead of repeatedly shifting elements, we can directly compute the final position of each element. If an element is at index i, after rotation it will be at index (i + k) % n. By using a temporary array to store the rotated result, we can place each element in its correct position in a single pass, then copy everything back.

Algorithm

  1. Create a temporary array tmp of the same size as nums.
  2. For each index i, place nums[i] at position (i + k) % n in tmp.
  3. Copy all elements from tmp back into nums.
class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        n = len(nums)
        tmp = [0] * n
        for i in range(n):
            tmp[(i + k) % n] = nums[i]

        nums[:] = tmp

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n) extra space.

3. Cyclic Traversal

Intuition

We can rotate in-place by following cycles. Starting from any position, we move the element to its destination, then move the displaced element to its destination, and so on until we return to the starting position. If the cycle doesn't cover all elements (which happens when n and k share a common divisor), we start a new cycle from the next position. This ensures every element is moved exactly once.

Algorithm

  1. Compute k = k % n and initialize a counter for how many elements have been placed.
  2. Start from index 0. For each starting index:
    • Save the element at the current position.
    • Move to the next position (current + k) % n, swap the saved element with the element there, and repeat.
    • Stop when we return to the starting index.
  3. If not all elements are placed, increment the starting index and repeat.
  4. Continue until all n elements have been moved.
class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        n = len(nums)
        k %= n
        count = start = 0

        while count < n:
            current = start
            prev = nums[start]
            while True:
                next_idx = (current + k) % n
                nums[next_idx], prev = prev, nums[next_idx]
                current = next_idx
                count += 1

                if start == current:
                    break
            start += 1

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(1)O(1) extra space.

4. Using Reverse

Intuition

A clever observation: rotating an array by k is equivalent to moving the last k elements to the front. We can achieve this with three reversals. First, reverse the entire array. Now the last k elements are at the front, but in reverse order. Reverse the first k elements to fix their order. Finally, reverse the remaining elements to restore their original order.

Algorithm

  1. Compute k = k % n.
  2. Reverse the entire array from index 0 to n - 1.
  3. Reverse the first k elements (from index 0 to k - 1).
  4. Reverse the remaining elements (from index k to n - 1).
class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        n = len(nums)
        k %= n

        def reverse(l: int, r: int) -> None:
            while l < r:
                nums[l], nums[r] = nums[r], nums[l]
                l, r = l + 1, r - 1

        reverse(0, n - 1)
        reverse(0, k - 1)
        reverse(k, n - 1)

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(1)O(1) extra space.

5. One Liner

Intuition

Many languages provide built-in ways to slice and concatenate arrays. We can simply take the last k elements and prepend them to the rest of the array. This leverages language features to express the rotation concisely, though under the hood it may use extra space.

Algorithm

  1. Compute the effective rotation k % n.
  2. Slice the array into two parts: the last k elements and the first n - k elements.
  3. Concatenate them in reverse order and assign back to the original array.
class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        nums[:] = nums[-k % len(nums):] + nums[:-k % len(nums)]

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(1)O(1) or O(n)O(n) depending on the language.

Common Pitfalls

Forgetting to Handle k Greater Than Array Length

When k is larger than the array length n, rotating by k is the same as rotating by k % n. Failing to normalize k with the modulo operation leads to unnecessary iterations or index out of bounds errors. Always compute k = k % n before proceeding.

Incorrect Reversal Boundaries in the Three-Reverse Approach

In the reverse approach, the three reversals must use precise boundaries: reverse the entire array, then reverse [0, k-1], then reverse [k, n-1]. Using wrong indices like reversing [0, k] instead of [0, k-1] shifts the boundary incorrectly and produces a wrong result.

Modifying Array While Iterating Without Proper Tracking

In the cyclic replacement approach, failing to track how many elements have been placed can cause infinite loops or incomplete rotations. This happens when n and k share a common divisor, creating multiple cycles. Always maintain a counter and start new cycles until all elements are moved.