Facebook Pixel

1752. Check if Array Is Sorted and Rotated

Problem Description

You're given an array nums and need to determine if it could be a rotated version of a sorted array.

The problem asks you to check if the array was originally sorted in non-decreasing order (elements can be equal or increasing) and then possibly rotated some number of positions. A rotation means taking some elements from the beginning and moving them to the end while maintaining their order.

For example:

  • [1, 2, 3, 4, 5] rotated by 2 positions becomes [3, 4, 5, 1, 2]
  • [2, 2, 3, 1, 1] could be a rotation of [1, 1, 2, 2, 3]

The array may contain duplicate values, which means equal consecutive elements are allowed.

The solution works by counting how many times a value is smaller than its previous element (a "break point"). In a valid rotated sorted array, there can be at most one such break point:

  • If there are 0 break points, the array is already sorted
  • If there is 1 break point, the array is a rotated sorted array
  • If there are 2 or more break points, the array cannot be a rotated sorted array

The code sum(nums[i - 1] > v for i, v in enumerate(nums)) <= 1 counts these break points by comparing each element with its previous one (including wrapping from the last element to the first) and returns True if there's at most one break point.

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

Intuition

To understand if an array is a rotated sorted array, let's think about what happens when we rotate a sorted array.

When we take a sorted array like [1, 2, 3, 4, 5] and rotate it, we're essentially cutting it at some point and swapping the two parts. For instance, rotating by 2 gives us [3, 4, 5, 1, 2].

The key observation is: in the resulting array, there's exactly one place where the order "breaks" - where a larger number is followed by a smaller number. In our example, that's between 5 and 1. This is the rotation point.

If we scan through the array and count how many times we see this pattern (where nums[i-1] > nums[i]), we can determine:

  • 0 breaks: The array is already sorted and hasn't been rotated
  • 1 break: The array is a valid rotated sorted array
  • More than 1 break: The array cannot be a rotated sorted array because rotating a sorted array can only create one discontinuity

There's one subtle detail: when comparing elements, we also need to check the wrap-around from the last element to the first element. If nums[n-1] > nums[0], that counts as a break too. This handles cases where the rotation point is at the beginning or end.

The beauty of this approach is that we don't need to actually find where the rotation happened or reconstruct the original array - we just need to verify that the pattern matches what a rotated sorted array would look like.

Solution Approach

The solution uses a single-line implementation that elegantly counts the number of "breaks" in the array:

def check(self, nums: List[int]) -> bool:
    return sum(nums[i - 1] > v for i, v in enumerate(nums)) <= 1

Let's break down how this works:

1. Enumeration and Comparison

  • enumerate(nums) iterates through the array, giving us both the index i and value v at each position
  • For each element, we compare it with its previous element using nums[i - 1] > v

2. Handling the Wrap-Around

  • When i = 0, the expression nums[i - 1] becomes nums[-1], which in Python refers to the last element
  • This automatically handles the comparison between the last and first elements, checking if there's a break at the rotation boundary

3. Counting Breaks

  • The generator expression nums[i - 1] > v for i, v in enumerate(nums) produces True (1) when there's a break and False (0) otherwise
  • sum() counts the total number of True values, giving us the total number of breaks

4. Final Check

  • The condition <= 1 returns True if there's at most one break, confirming the array is either:
    • Already sorted (0 breaks)
    • A valid rotated sorted array (1 break)

Example Walkthrough:

  • For [3, 4, 5, 1, 2]: comparisons are (2,3), (3,4), (4,5), (5,1), (1,2). Only (5,1) is a break, so sum = 1, returns True
  • For [2, 1, 3, 4]: comparisons are (4,2), (2,1), (1,3), (3,4). Both (4,2) and (2,1) are breaks, so sum = 2, returns False

The algorithm runs in O(n) time with O(1) extra space, making it optimal for this problem.

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 the array [4, 5, 6, 7, 1, 2, 3].

This array is actually a rotated version of [1, 2, 3, 4, 5, 6, 7], rotated 4 positions to the right.

Step 1: Set up the comparisons We'll check each element against its previous element, including the wrap-around from last to first:

Index (i)Value (v)Previous Element (nums[i-1])Is Break? (nums[i-1] > v)
043 (nums[-1] = last element)No (3 < 4)
154No (4 < 5)
265No (5 < 6)
376No (6 < 7)
417Yes (7 > 1)
521No (1 < 2)
632No (2 < 3)

Step 2: Count the breaks Going through our comparisons:

  • Positions 0, 1, 2, 3, 5, 6: No breaks (contribute 0 to sum)
  • Position 4: One break where 7 > 1 (contributes 1 to sum)

Total sum = 1

Step 3: Apply the check Since sum(breaks) = 1 ≤ 1, the function returns True.

This confirms that [4, 5, 6, 7, 1, 2, 3] is indeed a valid rotated sorted array. The single break at position 4 represents the rotation point where the array was "cut" and the parts were swapped.

Solution Implementation

1class Solution:
2    def check(self, nums: List[int]) -> bool:
3        """
4        Check if the array is a rotated sorted array.
5        A rotated sorted array has at most one position where the previous element
6        is greater than the current element (the rotation point).
7      
8        Args:
9            nums: List of integers to check
10          
11        Returns:
12            True if nums is a rotated sorted array, False otherwise
13        """
14        # Count the number of positions where previous element > current element
15        # For index i, nums[i-1] refers to the previous element
16        # When i=0, nums[-1] is the last element (circular comparison)
17        break_count = 0
18      
19        for i, current_value in enumerate(nums):
20            # Check if previous element is greater than current element
21            # This creates a circular comparison (last element compares with first)
22            if nums[i - 1] > current_value:
23                break_count += 1
24      
25        # A valid rotated sorted array has at most 1 break point
26        return break_count <= 1
27
1class Solution {
2    /**
3     * Checks if the array is a rotation of a sorted array.
4     * A sorted array rotated at some pivot will have at most one position
5     * where the current element is greater than the next element.
6     * 
7     * @param nums the input array to check
8     * @return true if the array is a valid rotation of a sorted array, false otherwise
9     */
10    public boolean check(int[] nums) {
11        // Count the number of "breaks" where current element > next element
12        int breakCount = 0;
13        int arrayLength = nums.length;
14      
15        // Iterate through all elements and compare with the next element (circular)
16        for (int i = 0; i < arrayLength; i++) {
17            // Use modulo to wrap around to index 0 when reaching the last element
18            int nextIndex = (i + 1) % arrayLength;
19          
20            // Check if current element breaks the non-decreasing order
21            if (nums[i] > nums[nextIndex]) {
22                breakCount++;
23            }
24        }
25      
26        // Valid rotation has at most 1 break point
27        return breakCount <= 1;
28    }
29}
30
1class Solution {
2public:
3    bool check(vector<int>& nums) {
4        // Count the number of positions where the current element is greater than the next element
5        // Using modulo to handle circular comparison (last element compared with first)
6        int breakCount = 0;
7        int n = nums.size();
8      
9        for (int i = 0; i < n; ++i) {
10            // Check if current element is greater than the next element (circular)
11            // nums[(i + 1) % n] handles wrap-around: when i = n-1, it compares with nums[0]
12            if (nums[i] > nums[(i + 1) % n]) {
13                breakCount++;
14            }
15        }
16      
17        // A sorted and rotated array can have at most 1 break point
18        // 0 breaks: array is already sorted
19        // 1 break: array is sorted and rotated
20        // >1 breaks: array is not sorted
21        return breakCount <= 1;
22    }
23};
24
1/**
2 * Checks if the array can be sorted by rotating it
3 * An array is considered valid if it has at most one "break point"
4 * where a larger element is followed by a smaller element
5 * @param nums - The input array of numbers to check
6 * @returns true if the array has at most one break point, false otherwise
7 */
8function check(nums: number[]): boolean {
9    const arrayLength: number = nums.length;
10  
11    // Count the number of positions where current element is greater than next element
12    // Using modulo to wrap around and compare last element with first element
13    const breakPointCount: number = nums.reduce((accumulator: number, currentValue: number, currentIndex: number) => {
14        const nextIndex: number = (currentIndex + 1) % arrayLength;
15        const isBreakPoint: boolean = currentValue > nums[nextIndex];
16        return accumulator + (isBreakPoint ? 1 : 0);
17    }, 0);
18  
19    // Array is valid if it has at most one break point
20    return breakPointCount <= 1;
21}
22

Time and Space Complexity

Time Complexity: O(n) where n is the length of the input array nums. The code iterates through the array once using enumerate(), and for each element, it performs a constant-time comparison (nums[i - 1] > v). The sum() function processes all n comparisons, resulting in linear time complexity.

Space Complexity: O(1). The generator expression (nums[i - 1] > v for i, v in enumerate(nums)) doesn't create an intermediate list but yields values one at a time to the sum() function. Only a constant amount of extra space is used for the loop variables (i, v) and the accumulator in sum(), regardless of the input size.

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

Common Pitfalls

1. Misunderstanding the Rotation Point Check

Pitfall: Assuming that checking only adjacent elements (without the wrap-around) is sufficient. Some might write:

def check(self, nums: List[int]) -> bool:
    break_count = 0
    for i in range(1, len(nums)):  # Starting from 1, missing wrap-around
        if nums[i-1] > nums[i]:
            break_count += 1
    return break_count <= 1

This fails for cases like [2, 1] where the only break is between the last and first element.

Solution: Always include the wrap-around comparison between the last and first element. The original solution handles this elegantly with nums[i-1] when i=0, which automatically becomes nums[-1].

2. Edge Case with Single Element

Pitfall: Not considering arrays with only one element. While the given solution handles this correctly, manual implementations might fail:

def check(self, nums: List[int]) -> bool:
    if len(nums) == 0:  # Unnecessary check that might miss single element case
        return True
    # ... rest of logic

Solution: The original solution naturally handles single-element arrays since the comparison nums[-1] > nums[0] compares the element with itself, which is always False.

3. Strict vs Non-Strict Inequality

Pitfall: Using >= instead of > in the comparison:

def check(self, nums: List[int]) -> bool:
    return sum(nums[i - 1] >= v for i, v in enumerate(nums)) <= 1  # Wrong!

This would incorrectly count equal consecutive elements as breaks. For example, [1, 1, 1] would have 3 "breaks" and return False.

Solution: Use strict inequality (>) to only count actual decreases, not equal values. The array [1, 1, 2, 2, 3] should be valid since equal consecutive elements are allowed in a sorted array.

4. Off-by-One Error in Manual Implementation

Pitfall: When manually implementing the circular check, forgetting to compare the last element with the first:

def check(self, nums: List[int]) -> bool:
    break_count = 0
    # Check all adjacent pairs
    for i in range(1, len(nums)):
        if nums[i-1] > nums[i]:
            break_count += 1
    # Forgot to check nums[-1] > nums[0]!
    return break_count <= 1

Solution: Either use the elegant enumeration approach that automatically handles wrap-around, or explicitly add the boundary check:

def check(self, nums: List[int]) -> bool:
    break_count = 0
    n = len(nums)
    for i in range(n):
        if nums[(i-1) % n] > nums[i]:  # Using modulo for clarity
            break_count += 1
    return break_count <= 1
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which two pointer techniques do you use to check if a string is a palindrome?


Recommended Readings

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

Load More