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.
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:
- Whether the array can be sorted at all (by checking if everything after the break point is sorted and fits before the beginning)
- 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 withn - 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 EvaluatorExample 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
✓, incrementi = 2
- Check
nums[1] < nums[2]
:4 < 5
✓, incrementi = 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 ati = 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 wherenums[i-1] >= nums[i]
or reaches the end. This loop runs at mostn-1
iterations. - The second while loop starts from index
i+1
and continues while the elements are in ascending order and less thannums[0]
, incrementingk
. This loop also runs at mostn-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
, andk
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:
- Explicitly handles the already-sorted case early
- Separates the validation logic for clarity
- Properly validates both ascending order within the second segment and the wraparound condition
- Adds defensive checks to prevent index errors
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
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!