183 lines
8.4 KiB
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
|
|
|
|
|
|
|
|
**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`
|
|
|
|
|
|
|
|
**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
|
|
|
|
|
|
|
|
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.
|