Facebook Pixel

2855. Minimum Right Shifts to Sort the Array

Problem Description

You have an array nums of length n containing distinct positive integers (indexed from 0). Your task is to determine the minimum number of right shifts needed to sort the array in ascending order. If it's impossible to sort the array using only right shifts, return -1.

A right shift operation moves every element one position to the right in a circular manner. Specifically, the element at index i moves to index (i + 1) % n. This means the last element wraps around to become the first element.

For example, if nums = [3, 4, 5, 1, 2]:

  • After 1 right shift: [2, 3, 4, 5, 1]
  • After 2 right shifts: [1, 2, 3, 4, 5] (sorted)

The solution works by finding the rotation point in the array. It first traverses from the beginning to find where the sorted sequence breaks (where nums[i-1] > nums[i]). Then it checks if the remaining elements form a valid continuation that could wrap around to the beginning. If the array can be sorted through rotation, the answer is n - i where i is the position where the first sorted sequence ends. Otherwise, it returns -1.

The key insight is that for an array to be sortable through right shifts, it must consist of at most two sorted segments where the second segment (if it exists) contains values smaller than the first element of the first segment.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Think about what happens when we perform right shifts on an array. Each right shift moves all elements one position to the right circularly. If we keep doing this, we're essentially rotating the array. The key observation is that if an array can be sorted through right shifts, it must already be "almost sorted" in a specific way.

Consider a sorted array that has been rotated some number of times. What does it look like? It would have two parts: a sorted sequence from some point to the end, followed by another sorted sequence from the beginning that continues where the first one left off. For example, [3, 4, 5, 1, 2] is essentially [1, 2, 3, 4, 5] rotated left by 2 positions (or right by 3 positions).

This means the original array must have at most one "break point" where the ascending order is violated. If we find this break point, we can determine:

  1. Whether the array can be sorted at all (by checking if everything after the break point is sorted and fits before the beginning)
  2. How many right shifts are needed (which would be n - position_of_break)

The algorithm naturally follows: we scan for where the first sorted sequence ends (where nums[i-1] > nums[i]). Then we verify that everything after this point forms a valid sorted sequence that could wrap around to the front. The condition nums[k] < nums[0] ensures that elements in the second part are smaller than the first element, maintaining the sorted order when rotated.

If we traverse the entire array without finding a break, the array is already sorted (return 0). If we find exactly one valid break point, we can calculate the shifts needed. If the second part doesn't maintain the required properties, sorting is impossible (return -1).

Solution Approach

The implementation uses a two-pointer traversal approach to identify and validate the rotation point in the array.

Step 1: Find the first break point

We initialize a pointer i = 1 and traverse the array from left to right, checking if each element is greater than the previous one (nums[i - 1] < nums[i]). We continue until we either:

  • Reach the end of the array (meaning it's already sorted)
  • Find a position where nums[i - 1] >= nums[i] (the break point)

This gives us the end position of the first sorted segment.

Step 2: Validate the second segment

Starting from k = i + 1, we traverse the remaining array to ensure:

  • The elements continue to be in ascending order (nums[k - 1] < nums[k])
  • Each element is less than the first element of the array (nums[k] < nums[0])

The second condition is crucial because when we perform right shifts, these elements will eventually wrap around to the beginning and must be smaller than nums[0] to maintain sorted order.

Step 3: Determine the result

After the second traversal:

  • If k < n (didn't reach the end), it means either the second segment isn't sorted or contains elements that are too large to wrap around properly. Return -1.
  • If k == n (reached the end successfully), the array can be sorted with n - i right shifts.

The special case where i == n (no break point found) means the array is already sorted, and n - i = 0 right shifts are needed, which is correct.

Time Complexity: O(n) - we traverse the array at most twice
Space Complexity: O(1) - only using a few variables for indices

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with nums = [3, 4, 5, 1, 2]:

Step 1: Find the first break point

  • Start with i = 1
  • Check nums[0] < nums[1]: 3 < 4 ✓, increment i = 2
  • Check nums[1] < nums[2]: 4 < 5 ✓, increment i = 3
  • Check nums[2] < nums[3]: 5 < 1 ✗, found break point!
  • First sorted segment ends at position i = 3

Step 2: Validate the second segment

  • Start with k = i + 1 = 4
  • Check nums[3] < nums[4]: 1 < 2 ✓ (sorted)
  • Check nums[4] < nums[0]: 2 < 3 ✓ (can wrap around)
  • Increment k = 5
  • Since k == n, we've successfully validated the entire second segment

Step 3: Calculate result

  • Array can be sorted with n - i = 5 - 3 = 2 right shifts

Verification:

  • Original: [3, 4, 5, 1, 2]
  • After 1 right shift: [2, 3, 4, 5, 1]
  • After 2 right shifts: [1, 2, 3, 4, 5] ✓ Sorted!

Now let's see an impossible case with nums = [3, 1, 4, 2]:

Step 1: Find the first break point

  • Start with i = 1
  • Check nums[0] < nums[1]: 3 < 1 ✗, found break point at i = 1

Step 2: Validate the second segment

  • Start with k = i + 1 = 2
  • Check nums[1] < nums[2]: 1 < 4 ✓ (sorted)
  • Check nums[2] < nums[0]: 4 < 3 ✗ (too large to wrap around!)
  • Since the condition fails, return -1

The array cannot be sorted through right shifts because 4 is larger than 3, so when rotated, it would break the sorted order.

Solution Implementation

1class Solution:
2    def minimumRightShifts(self, nums: List[int]) -> int:
3        n = len(nums)
4      
5        # Find the first position where the ascending order breaks
6        # This identifies the end of the first sorted segment
7        break_point = 1
8        while break_point < n and nums[break_point - 1] < nums[break_point]:
9            break_point += 1
10      
11        # Check if the remaining elements form a sorted sequence
12        # that is smaller than the first element (to ensure rotation validity)
13        current_position = break_point + 1
14        while current_position < n and nums[current_position - 1] < nums[current_position] < nums[0]:
15            current_position += 1
16      
17        # If we haven't reached the end, the array cannot be sorted by rotation
18        # Otherwise, return the number of right shifts needed (n - break_point)
19        return -1 if current_position < n else n - break_point
20
1class Solution {
2    public int minimumRightShifts(List<Integer> nums) {
3        int n = nums.size();
4      
5        // Find the first position where the array stops being sorted in ascending order
6        // This identifies the potential rotation point
7        int breakPoint = 1;
8        while (breakPoint < n && nums.get(breakPoint - 1) < nums.get(breakPoint)) {
9            breakPoint++;
10        }
11      
12        // Check if the remaining elements after the break point are:
13        // 1. Still sorted in ascending order
14        // 2. All smaller than the first element (to ensure valid rotation)
15        int currentIndex = breakPoint + 1;
16        while (currentIndex < n && 
17               nums.get(currentIndex - 1) < nums.get(currentIndex) && 
18               nums.get(currentIndex) < nums.get(0)) {
19            currentIndex++;
20        }
21      
22        // If we haven't traversed the entire array, it means the array cannot be sorted
23        // by right shifts (either not properly rotated or has multiple break points)
24        // Otherwise, return the number of right shifts needed (n - breakPoint)
25        return currentIndex < n ? -1 : n - breakPoint;
26    }
27}
28
1class Solution {
2public:
3    int minimumRightShifts(vector<int>& nums) {
4        int n = nums.size();
5      
6        // Find the rotation point where the sorted order breaks
7        // This identifies where the array was potentially rotated
8        int rotationPoint = 1;
9        while (rotationPoint < n && nums[rotationPoint - 1] < nums[rotationPoint]) {
10            ++rotationPoint;
11        }
12      
13        // Verify that the remaining elements after rotation point are:
14        // 1. In sorted order among themselves
15        // 2. All less than the first element (to ensure valid rotation)
16        int currentIndex = rotationPoint + 1;
17        while (currentIndex < n && 
18               nums[currentIndex - 1] < nums[currentIndex] && 
19               nums[currentIndex] < nums[0]) {
20            ++currentIndex;
21        }
22      
23        // If we didn't reach the end, the array cannot be sorted by right shifts
24        // Otherwise, return the number of right shifts needed (elements from rotation point to end)
25        return currentIndex < n ? -1 : n - rotationPoint;
26    }
27};
28
1/**
2 * Finds the minimum number of right shifts needed to sort an array
3 * @param nums - The input array of numbers
4 * @returns The minimum number of right shifts, or -1 if impossible
5 */
6function minimumRightShifts(nums: number[]): number {
7    const arrayLength: number = nums.length;
8  
9    // Find the first position where the array stops being sorted in ascending order
10    let breakPoint: number = 1;
11    while (breakPoint < arrayLength && nums[breakPoint - 1] < nums[breakPoint]) {
12        breakPoint++;
13    }
14  
15    // Check if the remaining elements after the break point are:
16    // 1. Sorted in ascending order among themselves
17    // 2. All smaller than the first element (to maintain sorted order after rotation)
18    let currentIndex: number = breakPoint + 1;
19    while (currentIndex < arrayLength && 
20           nums[currentIndex - 1] < nums[currentIndex] && 
21           nums[currentIndex] < nums[0]) {
22        currentIndex++;
23    }
24  
25    // If we didn't reach the end of array, it means the array cannot be sorted by right shifts
26    // Otherwise, return the number of right shifts needed (elements from break point to end)
27    return currentIndex < arrayLength ? -1 : arrayLength - breakPoint;
28}
29

Time and Space Complexity

Time Complexity: O(n)

The algorithm uses two while loops that traverse the array sequentially:

  • The first while loop starts from index 1 and continues while the array is in ascending order, incrementing i until it finds a position where nums[i-1] >= nums[i] or reaches the end. This loop runs at most n-1 iterations.
  • The second while loop starts from index i+1 and continues while the elements are in ascending order and less than nums[0], incrementing k. This loop also runs at most n-i-1 iterations.

Since each element is visited at most once across both loops, and the loops run sequentially (not nested), the total number of operations is linear with respect to the array size. Therefore, the time complexity is O(n).

Space Complexity: O(1)

The algorithm only uses a constant amount of extra space:

  • Variables n, i, and k are integer variables that store indices or the array length.
  • No additional data structures (arrays, lists, or other collections) are created.
  • The space usage does not depend on the input size.

Therefore, the space complexity is O(1), which represents constant space usage.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Missing Edge Case: Already Sorted Array

The current implementation has a subtle bug when the array is already sorted. When break_point reaches n (no break found), the code sets current_position = n + 1, which causes the while loop condition current_position < n to be false immediately. This makes the function return n - n = 0, which is correct by coincidence, but the logic is flawed.

Problem Example:

nums = [1, 2, 3, 4, 5]  # Already sorted
# break_point becomes 5
# current_position starts at 6
# while loop doesn't execute
# Returns 5 - 5 = 0 (correct, but logic is wrong)

2. Incorrect Validation of Wraparound Condition

The condition nums[current_position] < nums[0] in the second while loop is too restrictive. It should check against the last element of the first segment (nums[break_point - 1]) to ensure proper continuation after rotation.

Problem Example:

nums = [4, 5, 1, 2, 3]
# First segment: [4, 5], Second segment: [1, 2, 3]
# The code checks if 3 < 4, which is true
# But we also need to ensure 3 < 5 for proper sorting after rotation

3. Off-by-One Error in Boundary Check

When break_point equals n, accessing nums[break_point] in the subsequent code would cause an index out of bounds error if not handled properly.

Corrected Solution

class Solution:
    def minimumRightShifts(self, nums: List[int]) -> int:
        n = len(nums)
      
        # Find the rotation point (where ascending order first breaks)
        rotation_point = 1
        while rotation_point < n and nums[rotation_point - 1] < nums[rotation_point]:
            rotation_point += 1
      
        # If no break point found, array is already sorted
        if rotation_point == n:
            return 0
      
        # Validate the second segment
        # 1. Must be sorted in ascending order
        # 2. Last element must be less than first element for valid rotation
        current = rotation_point
        while current < n:
            # Check ascending order within second segment
            if current > rotation_point and nums[current - 1] >= nums[current]:
                return -1
            # Check wraparound condition
            if nums[current] >= nums[0]:
                return -1
            current += 1
      
        # Additional check: last element of array must be less than first element
        # of the first sorted segment to ensure continuity after rotation
        if nums[n - 1] >= nums[0]:
            return -1
          
        return n - rotation_point

Key Improvements:

  1. Explicitly handles the already-sorted case early
  2. Separates the validation logic for clarity
  3. Properly validates both ascending order within the second segment and the wraparound condition
  4. Adds defensive checks to prevent index errors
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

A person thinks of a number between 1 and 1000. You may ask any number questions to them, provided that the question can be answered with either "yes" or "no".

What is the minimum number of questions you needed to ask so that you are guaranteed to know the number that the person is thinking?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More