Files
codetutor/backend/data/questions/check-if-array-is-sorted-and-rotated.yaml

183 lines
8.4 KiB
YAML

title: Check if Array Is Sorted and Rotated
slug: check-if-array-is-sorted-and-rotated
difficulty: easy
leetcode_id: 1752
leetcode_url: https://leetcode.com/problems/check-if-array-is-sorted-and-rotated/
categories:
- arrays
patterns:
- two-pointers
function_signature: "def check(nums: list[int]) -> bool:"
test_cases:
visible:
- input: { nums: [3, 4, 5, 1, 2] }
expected: true
- input: { nums: [2, 1, 3, 4] }
expected: false
- input: { nums: [1, 2, 3] }
expected: true
hidden:
- input: { nums: [1] }
expected: true
- input: { nums: [2, 1] }
expected: true
- input: { nums: [1, 1, 1] }
expected: true
- input: { nums: [3, 4, 5, 1, 2, 2] }
expected: false
- input: { nums: [6, 10, 6] }
expected: true
- input: { nums: [1, 2, 3, 4, 5] }
expected: true
- input: { nums: [5, 1, 2, 3, 4] }
expected: true
description: |
Given an array `nums`, return `true` *if the array was originally sorted in non-decreasing order, then rotated **some** number of positions (including zero)*. Otherwise, return `false`.
There may be **duplicates** in the original array.
**Note:** An array `A` rotated by `x` positions results in an array `B` of the same length such that `B[i] == A[(i+x) % A.length]` for every valid index `i`.
constraints: |
- `1 <= nums.length <= 100`
- `1 <= nums[i] <= 100`
examples:
- input: "nums = [3,4,5,1,2]"
output: "true"
explanation: "[1,2,3,4,5] is the original sorted array. You can rotate the array by x = 2 positions to begin on the element of value 3: [3,4,5,1,2]."
- input: "nums = [2,1,3,4]"
output: "false"
explanation: "There is no sorted array once rotated that can make nums."
- input: "nums = [1,2,3]"
output: "true"
explanation: "[1,2,3] is the original sorted array. You can rotate the array by x = 0 positions (i.e. no rotation) to make nums."
explanation:
intuition: |
Think of rotation like taking a deck of cards that's in order and cutting it somewhere in the middle — you move the top portion to the bottom. The cards are still in order within each portion, but there's exactly **one place** where the sequence "breaks" (where the bottom of the cut meets the top).
In a sorted-then-rotated array, you'll see the numbers increasing (or staying the same for duplicates), then suddenly **drop** to a smaller value, then continue increasing again. For example, `[3,4,5,1,2]` goes up from 3→4→5, then drops to 1, then continues up from 1→2.
The key insight is: **a valid rotated sorted array can have at most one "breakpoint"** — one place where `nums[i] > nums[i+1]`. If you find two or more such breakpoints, the array couldn't have come from rotating a sorted array.
There's one subtle catch: we also need to check the **wrap-around** from the last element back to the first. If the array was rotated (not just sorted), the last element should be ≤ the first element to complete the "circle" back to sorted order.
approach: |
We solve this by **counting inversions** (places where the order breaks):
**Step 1: Initialise a counter**
- `inversions`: Set to `0` to count how many times the sequence breaks
&nbsp;
**Step 2: Scan for breakpoints in the array**
- Iterate through indices `0` to `n-1`
- For each index `i`, compare `nums[i]` with `nums[(i+1) % n]`
- Using modulo `% n` handles the wrap-around comparison (last element vs first)
- If `nums[i] > nums[(i+1) % n]`, increment `inversions`
&nbsp;
**Step 3: Return the result**
- If `inversions <= 1`, return `true` — the array is a valid rotated sorted array
- If `inversions > 1`, return `false` — too many breakpoints to be a rotation
&nbsp;
This works because a sorted array has zero inversions, and rotating it introduces exactly one inversion at the rotation point. The wrap-around check using modulo ensures we verify the circular property.
common_pitfalls:
- title: Forgetting the Wrap-Around Check
description: |
A common mistake is only checking adjacent pairs without considering the wrap-around from the last element to the first.
For example, `[2,3,1]` has one inversion (3→1), but we also need to verify that `1 <= 2` (last to first) for it to be a valid rotation. Using `(i+1) % n` automatically handles this by including the last-to-first comparison in our loop.
wrong_approach: "Only check nums[i] > nums[i+1] for i < n-1"
correct_approach: "Use modulo to include the wrap-around: nums[i] > nums[(i+1) % n]"
- title: Off-by-One in Loop Bounds
description: |
If you iterate `i` from `0` to `n-2` (exclusive of the last element) and then separately check the wrap-around, you might miss edge cases or double-count.
The cleaner approach is to iterate `i` from `0` to `n-1` and always use modulo arithmetic. This uniformly handles all comparisons including the wrap-around.
wrong_approach: "Separate logic for internal pairs and wrap-around"
correct_approach: "Single loop with modulo for uniform handling"
- title: Confusing "Rotated" with Unsorted
description: |
The problem specifically asks about arrays that were **sorted then rotated**. An array like `[2,1,3,4]` has one inversion (2→1), but checking the wrap-around (4→2) reveals a second inversion. Two inversions means it's not a valid rotated sorted array.
Always count all inversions including the circular wrap-around before deciding.
key_takeaways:
- "**Rotation creates exactly one breakpoint**: A sorted array rotated `k` positions has exactly one place where `nums[i] > nums[i+1]`"
- "**Modulo for circular arrays**: Using `(i+1) % n` is a clean way to handle wrap-around comparisons in circular/rotated array problems"
- "**Count inversions pattern**: Counting how many times a property is violated helps validate structural constraints"
- "**Foundation for binary search**: Understanding rotated sorted arrays is key to problems like 'Search in Rotated Sorted Array'"
time_complexity: "O(n). We traverse the array once, checking each adjacent pair including the wrap-around."
space_complexity: "O(1). We only use a single counter variable regardless of input size."
solutions:
- approach_name: Count Inversions
is_optimal: true
code: |
def check(nums: list[int]) -> bool:
n = len(nums)
inversions = 0
# Check all adjacent pairs including wrap-around (last to first)
for i in range(n):
# Use modulo to wrap index back to 0 after last element
if nums[i] > nums[(i + 1) % n]:
inversions += 1
# Early exit: more than one inversion means not valid
if inversions > 1:
return False
# Valid if at most one inversion (rotation point)
return True
explanation: |
**Time Complexity:** O(n) — Single pass through the array with early exit optimisation.
**Space Complexity:** O(1) — Only one integer counter used.
We iterate through all elements, comparing each to its next neighbor (with wrap-around handled by modulo). A valid rotated sorted array has at most one "drop" point. The early exit when we find a second inversion provides a minor optimisation.
- approach_name: Find Pivot and Verify
is_optimal: false
code: |
def check(nums: list[int]) -> bool:
n = len(nums)
pivot = -1
# Find the rotation pivot (where sequence breaks)
for i in range(n - 1):
if nums[i] > nums[i + 1]:
# Found a breakpoint
if pivot != -1:
# Second breakpoint found - not valid
return False
pivot = i
# If no pivot found, array is fully sorted (rotation by 0)
if pivot == -1:
return True
# Verify wrap-around: last element should be <= first
return nums[n - 1] <= nums[0]
explanation: |
**Time Complexity:** O(n) — Single pass to find pivot, then O(1) wrap-around check.
**Space Complexity:** O(1) — Only storing the pivot index.
This approach explicitly finds the pivot point (rotation boundary) and then verifies the wrap-around condition separately. While correct, it's slightly more verbose than the modulo approach. The logic is: find at most one breakpoint in the array body, then confirm the circular property holds.