Search in Rotated Sorted Array (LeetCode 33)
Before: arrange the unique integers from smallest to largest.
Change: the list may stay untouched, or undergo a left rotation. A rotation cuts it at an undisclosed position k (1 <= k < nums.length) and moves the prefix behind the suffix without changing the order inside either piece.
Received: nums contains that final arrangement. A cut at 3 takes the prefix [0, 1, 2] off [0, 1, 2, 4, 5, 6, 7] and places it after [4, 5, 6, 7], giving [4, 5, 6, 7, 0, 1, 2]. An unrotated list is also a valid input.
Locate the integer target in the received list. Report its zero-based index if present; report -1 otherwise. The required running time is O(log n), where n is the length of nums.
Worked Examples
nums = [4,5,6,7,0,1,2], target = 04nums = [4,5,6,7,0,1,2], target = 3-1nums = [1], target = 0-1⚖️Formal Constraints & Bounds
1 <= nums.length <= 5000-104 <= nums[i] <= 104All values of
numsare unique.numsis an ascending array that is possibly rotated.-104 <= target <= 104
Core Insight
The first value nums[0] tells which run an index is in, so one Lower Bound finds the pivot. Read from the pivot, the array is sorted, so a second Lower Bound on the positions (pivot + v) % n finds the target.
Real-World Scenario & Production Applications
Ring buffers and circular logs (like distributed event streams) wrap around when full. To find a record by its counter value, you find the oldest entry, the pivot, and then search the sorted order that starts there, both in logarithmic time.
Subproblems & Recurrence Decomposition4 Phases
The first value nums[0] tells which run an index is in: values in the first run are at least nums[0], and values in the second run are smaller. So the check nums[i] < nums[0] is False and then True. Copies of nums[0] at the end of the array look like the first run, so the trim removes them first.
n = len(nums)
hi = n
while hi > 1 and nums[hi - 1] == nums[0]:
hi -= 1 # ignore tail copies of nums[0]
def smaller_than_first(i):
return nums[i] < nums[0]Step-by-Step Execution Trace Table
| Step | Search | Range [lo, hi) | mid | Check | Decision | New range [lo, hi) |
|---|---|---|---|---|---|---|
| 1 | Pivot | [0, 7) | 3 | nums[3] < nums[0]: 7 < 4 → False | 7 is in the first run, so the pivot is to its right: lo = mid + 1 | [4, 7) |
| 2 | Pivot | [4, 7) | 5 | 1 < 4 → True | 1 is in the second run, so keep mid: hi = mid | [4, 5) |
| 3 | Pivot | [4, 5) | 4 | 0 < 4 → True | keep mid: hi = mid | [4, 4) |
| Pivot | [4, 4) | The range is empty, so pivot = 4 % 7 = 4. | ||||
| 4 | Target | [0, 7) | 3 | position 3 is index (4 + 3) % 7 = 0, nums[0] = 4 >= 0 → True | keep mid: hi = mid | [0, 3) |
| 5 | Target | [0, 3) | 1 | index 5, nums[5] = 1 >= 0 → True | hi = mid | [0, 1) |
| 6 | Target | [0, 1) | 0 | index 4, nums[4] = 0 >= 0 → True | hi = mid | [0, 0) |
| Target | [0, 0) | The range is empty: v = 0, index (4 + 0) % 7 = 4, nums[4] == 0. Return 4. |
The trace for nums = [4, 5, 6, 7, 0, 1, 2] and target = 0, step by step:
- The array has no repeated values, so the trim does nothing and
hi = 7. - Steps 1 to 3 find the pivot: the first index whose value is smaller than
nums[0] = 4. It is index 4. - Steps 4 to 6 search the sorted order that starts at index 4. Position 0 is index 4, and it holds 0, the first value that is at least 0.
- The look:
v < nandnums[4] == 0, so the answer is index 4.
| 1 | n = len(nums) |
| 2 | hi = n |
| 3 | while hi > 1 and nums[hi - 1] == nums[0]: |
| 4 | hi -= 1 |
| 5 | pivot = binary_search_lower_bound(0, hi, smaller_than_first) % n |
| 6 | v = binary_search_lower_bound(0, n, at_least_target) |
| 7 | if v < n and nums[(pivot + v) % n] == target: |
| 8 | return (pivot + v) % n |
| 9 | return -1 |
Conceptual Narrative
🧭 Conceptual Foundation & Pattern Intuition
A rotated sorted array is a sorted list that was cut at one place and joined again with the two pieces swapped. The first value, nums[0], tells which piece an index is in: the first piece holds values that are at least nums[0], and the second piece holds smaller values. So one Lower Bound search finds the pivot, the start of the sorted order. Then a second Lower Bound search runs on the sorted order that starts at the pivot.
🕰️ The Analogy: A Clock That Was Turned
Imagine the numbers 0 to 11 on a clock face, but someone turned the clock, so the smallest number is not at the top. You can still read the numbers in order: start at the smallest number and go around the face. The pivot is that start. Position v in the sorted order is the number v steps around from the pivot, which is index (pivot + v) % n.
🔑 Why It Works
The pivot is the start of the sorted order, and the map (pivot + v) % n visits every index once, in sorted order. So the check "is the value at position v at least target?" is False and then True, and its first True is where target would be. When values repeat, copies of nums[0] at the end look like the first run, so the trim removes them first. The trim costs O(N) in the worst case, when every value is equal.
Setting
pivot = 0when no value is smallerForgetting
% nin the position mapSkipping the trim when values repeat
Writing
<=instead of<in the pivot checkReturning the position without checking the value
Reading
nums[v]instead ofnums[(pivot + v) % n]
4-Phase Thought Process Model
Pattern Recognition Signals
The 10-second spot
A sorted array was rotated by an unknown amount, and you need a target's index in O(log N) time. That points to Rotated Array: the first value tells which run an index is in.
Formulating the Predicate & Invariants
Turning intuition into a boolean rule
Invariant: read from the pivot, the array is sorted, and position v is index (pivot + v) % n. The check nums[i] < nums[0] is False in the first run and True in the second run, so Lower Bound finds the pivot. The check nums[(pivot + v) % n] >= target is False and then True, so a second Lower Bound finds the target.
Silent Failure Traps & Edge Cases
Where confident candidates still lose points
Resetting the pivot to 0 when no value is smaller than
nums[0]: the first search returnshi, and% ntakes it back to an index (nums = [0, 1, 0, 0],target = 1returns-1with the reset).Forgetting
% nin the position map:nums[pivot + v]goes past the end of the list and raises anIndexError(nums = [4, 5, 6, 7, 0, 1, 2],target = 0).
The 60-Second Interview Pitch
Say this out loud before you type a single line
"This is Rotated Array. It runs Lower Bound twice. The first value, nums[0], tells me which run an index is in: values in the first run are at least nums[0], and values in the second run are smaller. So the check nums[i] < nums[0] is False and then True, and its first True is the pivot. I take the pivot modulo n, because when no value is smaller the search returns hi. Read from the pivot, the array is sorted, so position v is index (pivot + v) % n. A second Lower Bound finds the first position whose value is at least target, and one look tells me if it is target. If values can repeat, I first ignore the copies of nums[0] at the end of the array. The trap is resetting the pivot to 0 instead of taking it modulo n. That is
O(log n)time with distinct values,O(n)worst case with repeated values, andO(1)space."
Complexity & Mathematical Proof
O(log N)
Look at the two calls of Lower Bound: each runs while lo < hi: and keeps at most half of its range on every pass, so each takes at most ⌊log2 N⌋ + 1 passes with O(1) work per pass. Two searches are O(log N) in total. The trim loop while hi > 1 and nums[hi - 1] == nums[0] does no work when values are distinct, and walks the whole array when every value is equal, so with repeated values the worst case is O(N).
O(1)
Look at the memory used: both searches are iterative, so the call stack holds a few frames. We store only the integers n, hi, pivot, v, lo and mid. Auxiliary space is O(1).
Derivation Progression
nums[i] < nums[0] or nums[(pivot + v) % n] >= target
Each pass computes mid and asks one check once: O(1) work per pass.
lo = mid + 1 or hi = mid
Each search removes half of its remaining candidates per pass.
lo == hi ⟹ O(log N)
Halving N candidates down to none takes at most floor(log2 N) + 1 passes, for each of the two searches.
Variable Definitions
Total number of elements in the rotated sorted array
Memory Architecture & Bounds
O(1) Iterative while loop with zero recursion frames
O(1) Only scalar pointers (lo, hi, mid) in CPU registers
O(1) Returns an index
Boundary Best / Worst Cases
for distinct elements: the trim does no work.
.
when every value is equal: the trim loop walks the whole array. With distinct values the worst case is .
Recurrence Tree Topology
Senior SWE Deconstruction & Hardware Caveats
The statement gives an array "sorted in ascending order with unique values" that was "rotated at an unknown pivot", and asks for a target's index "in O(log n) time". This is Rotated Array: the first value nums[0] tells which run an index is in, so Lower Bound finds the pivot, and a second Lower Bound searches the sorted order that starts there.
The array length is , and all integers are guaranteed unique, so the trim loop does nothing. The first search runs over [0, n) and returns n when no value is smaller than nums[0]; % n takes it back to the pivot 0. The second search runs over the sorted positions [0, n).
The card's trap is resetting the pivot to 0 when no value is smaller than nums[0]: take it modulo n instead. Another is forgetting % n in the position map. At scale there is one more: if the array is a live circular log that keeps being written while you search, the wrap point moves between reads, so search a snapshot (or hold the writers off) or the answer can land on a record that is no longer the oldest.
Search in Rotated Sorted Array (LeetCode 33)
Before: arrange the unique integers from smallest to largest.
Change: the list may stay untouched, or undergo a left rotation. A rotation cuts it at an undisclosed position k (1 <= k < nums.length) and moves the prefix behind the suffix without changing the order inside either piece.
Received: nums contains that final arrangement. A cut at 3 takes the prefix [0, 1, 2] off [0, 1, 2, 4, 5, 6, 7] and places it after [4, 5, 6, 7], giving [4, 5, 6, 7, 0, 1, 2]. An unrotated list is also a valid input.
Locate the integer target in the received list. Report its zero-based index if present; report -1 otherwise. The required running time is O(log n), where n is the length of nums.
Worked Examples
nums = [4,5,6,7,0,1,2], target = 04nums = [4,5,6,7,0,1,2], target = 3-1nums = [1], target = 0-1⚖️Formal Constraints & Bounds
1 <= nums.length <= 5000-104 <= nums[i] <= 104All values of
numsare unique.numsis an ascending array that is possibly rotated.-104 <= target <= 104
Core Insight
The first value nums[0] tells which run an index is in, so one Lower Bound finds the pivot. Read from the pivot, the array is sorted, so a second Lower Bound on the positions (pivot + v) % n finds the target.
Real-World Scenario & Production Applications
Ring buffers and circular logs (like distributed event streams) wrap around when full. To find a record by its counter value, you find the oldest entry, the pivot, and then search the sorted order that starts there, both in logarithmic time.
Subproblems & Recurrence Decomposition4 Phases
The first value nums[0] tells which run an index is in: values in the first run are at least nums[0], and values in the second run are smaller. So the check nums[i] < nums[0] is False and then True. Copies of nums[0] at the end of the array look like the first run, so the trim removes them first.
n = len(nums)
hi = n
while hi > 1 and nums[hi - 1] == nums[0]:
hi -= 1 # ignore tail copies of nums[0]
def smaller_than_first(i):
return nums[i] < nums[0]Step-by-Step Execution Trace Table
| Step | Search | Range [lo, hi) | mid | Check | Decision | New range [lo, hi) |
|---|---|---|---|---|---|---|
| 1 | Pivot | [0, 7) | 3 | nums[3] < nums[0]: 7 < 4 → False | 7 is in the first run, so the pivot is to its right: lo = mid + 1 | [4, 7) |
| 2 | Pivot | [4, 7) | 5 | 1 < 4 → True | 1 is in the second run, so keep mid: hi = mid | [4, 5) |
| 3 | Pivot | [4, 5) | 4 | 0 < 4 → True | keep mid: hi = mid | [4, 4) |
| Pivot | [4, 4) | The range is empty, so pivot = 4 % 7 = 4. | ||||
| 4 | Target | [0, 7) | 3 | position 3 is index (4 + 3) % 7 = 0, nums[0] = 4 >= 0 → True | keep mid: hi = mid | [0, 3) |
| 5 | Target | [0, 3) | 1 | index 5, nums[5] = 1 >= 0 → True | hi = mid | [0, 1) |
| 6 | Target | [0, 1) | 0 | index 4, nums[4] = 0 >= 0 → True | hi = mid | [0, 0) |
| Target | [0, 0) | The range is empty: v = 0, index (4 + 0) % 7 = 4, nums[4] == 0. Return 4. |
The trace for nums = [4, 5, 6, 7, 0, 1, 2] and target = 0, step by step:
- The array has no repeated values, so the trim does nothing and
hi = 7. - Steps 1 to 3 find the pivot: the first index whose value is smaller than
nums[0] = 4. It is index 4. - Steps 4 to 6 search the sorted order that starts at index 4. Position 0 is index 4, and it holds 0, the first value that is at least 0.
- The look:
v < nandnums[4] == 0, so the answer is index 4.
| 1 | n = len(nums) |
| 2 | hi = n |
| 3 | while hi > 1 and nums[hi - 1] == nums[0]: |
| 4 | hi -= 1 |
| 5 | pivot = binary_search_lower_bound(0, hi, smaller_than_first) % n |
| 6 | v = binary_search_lower_bound(0, n, at_least_target) |
| 7 | if v < n and nums[(pivot + v) % n] == target: |
| 8 | return (pivot + v) % n |
| 9 | return -1 |
Conceptual Narrative
🧭 Conceptual Foundation & Pattern Intuition
A rotated sorted array is a sorted list that was cut at one place and joined again with the two pieces swapped. The first value, nums[0], tells which piece an index is in: the first piece holds values that are at least nums[0], and the second piece holds smaller values. So one Lower Bound search finds the pivot, the start of the sorted order. Then a second Lower Bound search runs on the sorted order that starts at the pivot.
🕰️ The Analogy: A Clock That Was Turned
Imagine the numbers 0 to 11 on a clock face, but someone turned the clock, so the smallest number is not at the top. You can still read the numbers in order: start at the smallest number and go around the face. The pivot is that start. Position v in the sorted order is the number v steps around from the pivot, which is index (pivot + v) % n.
🔑 Why It Works
The pivot is the start of the sorted order, and the map (pivot + v) % n visits every index once, in sorted order. So the check "is the value at position v at least target?" is False and then True, and its first True is where target would be. When values repeat, copies of nums[0] at the end look like the first run, so the trim removes them first. The trim costs O(N) in the worst case, when every value is equal.
Setting
pivot = 0when no value is smallerForgetting
% nin the position mapSkipping the trim when values repeat
Writing
<=instead of<in the pivot checkReturning the position without checking the value
Reading
nums[v]instead ofnums[(pivot + v) % n]
4-Phase Thought Process Model
Pattern Recognition Signals
The 10-second spot
A sorted array was rotated by an unknown amount, and you need a target's index in O(log N) time. That points to Rotated Array: the first value tells which run an index is in.
Formulating the Predicate & Invariants
Turning intuition into a boolean rule
Invariant: read from the pivot, the array is sorted, and position v is index (pivot + v) % n. The check nums[i] < nums[0] is False in the first run and True in the second run, so Lower Bound finds the pivot. The check nums[(pivot + v) % n] >= target is False and then True, so a second Lower Bound finds the target.
Silent Failure Traps & Edge Cases
Where confident candidates still lose points
Resetting the pivot to 0 when no value is smaller than
nums[0]: the first search returnshi, and% ntakes it back to an index (nums = [0, 1, 0, 0],target = 1returns-1with the reset).Forgetting
% nin the position map:nums[pivot + v]goes past the end of the list and raises anIndexError(nums = [4, 5, 6, 7, 0, 1, 2],target = 0).
The 60-Second Interview Pitch
Say this out loud before you type a single line
"This is Rotated Array. It runs Lower Bound twice. The first value, nums[0], tells me which run an index is in: values in the first run are at least nums[0], and values in the second run are smaller. So the check nums[i] < nums[0] is False and then True, and its first True is the pivot. I take the pivot modulo n, because when no value is smaller the search returns hi. Read from the pivot, the array is sorted, so position v is index (pivot + v) % n. A second Lower Bound finds the first position whose value is at least target, and one look tells me if it is target. If values can repeat, I first ignore the copies of nums[0] at the end of the array. The trap is resetting the pivot to 0 instead of taking it modulo n. That is
O(log n)time with distinct values,O(n)worst case with repeated values, andO(1)space."
Complexity & Mathematical Proof
O(log N)
Look at the two calls of Lower Bound: each runs while lo < hi: and keeps at most half of its range on every pass, so each takes at most ⌊log2 N⌋ + 1 passes with O(1) work per pass. Two searches are O(log N) in total. The trim loop while hi > 1 and nums[hi - 1] == nums[0] does no work when values are distinct, and walks the whole array when every value is equal, so with repeated values the worst case is O(N).
O(1)
Look at the memory used: both searches are iterative, so the call stack holds a few frames. We store only the integers n, hi, pivot, v, lo and mid. Auxiliary space is O(1).
Derivation Progression
nums[i] < nums[0] or nums[(pivot + v) % n] >= target
Each pass computes mid and asks one check once: O(1) work per pass.
lo = mid + 1 or hi = mid
Each search removes half of its remaining candidates per pass.
lo == hi ⟹ O(log N)
Halving N candidates down to none takes at most floor(log2 N) + 1 passes, for each of the two searches.
Variable Definitions
Total number of elements in the rotated sorted array
Memory Architecture & Bounds
O(1) Iterative while loop with zero recursion frames
O(1) Only scalar pointers (lo, hi, mid) in CPU registers
O(1) Returns an index
Boundary Best / Worst Cases
for distinct elements: the trim does no work.
.
when every value is equal: the trim loop walks the whole array. With distinct values the worst case is .
Recurrence Tree Topology
Senior SWE Deconstruction & Hardware Caveats
The statement gives an array "sorted in ascending order with unique values" that was "rotated at an unknown pivot", and asks for a target's index "in O(log n) time". This is Rotated Array: the first value nums[0] tells which run an index is in, so Lower Bound finds the pivot, and a second Lower Bound searches the sorted order that starts there.
The array length is , and all integers are guaranteed unique, so the trim loop does nothing. The first search runs over [0, n) and returns n when no value is smaller than nums[0]; % n takes it back to the pivot 0. The second search runs over the sorted positions [0, n).
The card's trap is resetting the pivot to 0 when no value is smaller than nums[0]: take it modulo n instead. Another is forgetting % n in the position map. At scale there is one more: if the array is a live circular log that keeps being written while you search, the wrap point moves between reads, so search a snapshot (or hold the writers off) or the answer can land on a record that is no longer the oldest.