Difficulty: Medium Pattern: Binary Search LeetCode: https://leetcode.com/problems/search-in-rotated-sorted-array/
Given the array nums of distinct integers sorted in ascending order and
then rotated an unknown number of times, and an integer target, return the
index of target if it is in the array, or -1 if it is not.
You must write an O(log n) solution.
Signature:
int search(int[] nums, int target)
Examples (verbatim from LeetCode):
Input: nums = [4,5,6,7,0,1,2], target = 0
Output: 4
Input: nums = [4,5,6,7,0,1,2], target = 3
Output: -1
Input: nums = [1], target = 0
Output: -1
A rotated sorted array is a sorted array with the last few elements moved to
the front, so it looks like two sorted chunks glued at one drop:
[4, 5, 6, 7, 0, 1, 2] is [4,5,6,7] followed by [0,1,2]. (LC 153 explains
rotation in full.) This problem asks the same thing as LC 704 – find a target’s
index – but on a rotated array, so a plain middle-comparison no longer tells
you which half to keep.
The saving grace is the same as in LC 153: at every step, at least one of the two halves is normally sorted. A sorted half is the only thing that lets us reason about where the target is, so each step asks two questions, always in this order:
nums[mid] against nums[lo].Trace nums = [4, 5, 6, 7, 0, 1, 2], target = 0 (answer index 4):
lo = 0, hi = 6, mid = 3, nums[mid] = 7. Which half is sorted?
nums[lo] = 4 <= nums[mid] = 7, so the left half [4,5,6,7] is sorted.
Is 0 inside [4, 7)? No – so 0 must be in the other half. Discard the
left: lo = 4.lo = 4, hi = 6, mid = 5, nums[mid] = 1. Which half is sorted?
nums[lo] = 0 <= nums[mid] = 1, so the left half [0, 1] is sorted. Is 0
inside [0, 1)? Yes – search there: hi = 4.lo = 4, hi = 4, mid = 4, nums[mid] = 0 == target – return 4.The invariant is LC 704’s: if the target is in the array, it lies in
[lo, hi]. The only difference is that, because the array is rotated, we
cannot decide which half to discard just by comparing nums[mid] to the target
– we must first identify the sorted half and test the target against its
known range. The classic beginner mistake is to compare nums[mid] to the
target before finding the sorted half; on a rotated array that comparison is
meaningless and sends you into the wrong half.
Pause and answer before expanding. A wrong first guess teaches more than a fast right one.
Q1 (recall). After the exact-match check fails, what is the FIRST thing the loop decides each iteration?
nums[lo] <= nums[mid]nums[mid]Q2 (comprehend). On nums = [4,5,6,7,0,1,2], target = 0, iteration 1 finds nums[mid] = 7 and the left half [4,5,6,7] is sorted. Why is the left half then DISCARDED (lo = mid + 1)?
[4, 7), so it must be in the other halffunction search(nums, target):
low <- 0
high <- length(nums) - 1
while low <= high:
mid <- low + (high - low) / 2
if nums[mid] equals target:
return mid
if nums[low] <= nums[mid]: # LEFT half is sorted
if nums[low] <= target < nums[mid]:
high <- mid - 1 # target in sorted left half
else:
low <- mid + 1 # target in the other half
else: # RIGHT half is sorted
if nums[mid] < target <= nums[high]:
low <- mid + 1 # target in sorted right half
else:
high <- mid - 1 # target in the other half
return -1
import java.util.*;
class Solution {
public int search(int[] nums, int target) {
int lo = 0, hi = nums.length - 1;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (nums[mid] == target) {
return mid;
}
if (nums[lo] <= nums[mid]) {
if (nums[lo] <= target && target < nums[mid]) {
hi = mid - 1;
} else {
lo = mid + 1;
}
} else {
if (nums[mid] < target && target <= nums[hi]) {
lo = mid + 1;
} else {
hi = mid - 1;
}
}
}
return -1;
}
}
This is Template A in shape (while (lo <= hi), ±1 moves), but the body does
the two-step “which half is sorted, then where is the target” reasoning. The
test nums[lo] <= nums[mid] (with <=, not <) handles the degenerate range
where lo == mid, which happens on two-element ranges – the equality ensures
we still classify a one-element left half as “sorted”. The membership bounds
deliberately exclude nums[mid] itself (target < nums[mid], not <=),
because the nums[mid] == target case already returned at the top of the loop.
Distinct values guarantee nums[lo] <= nums[mid] cleanly partitions the cases;
with duplicates (LC 81) the boundary becomes ambiguous and the worst case
degrades to O(n). The midpoint uses the overflow-safe form. As usual, the empty
case never reaches the loop and we fall through to return -1.
Time: O(log n) -- the range halves each iteration.
Space: O(1) -- three integer variables.
Step-by-step on nums = [4, 5, 6, 7, 0, 1, 2], target = 0 (expected 4).
“target in [a, b)?” reads as a <= target && target < b:
| Iter | lo | hi | mid | nums[mid] | which half sorted? | target in sorted half? | action |
|---|---|---|---|---|---|---|---|
| 1 | 0 | 6 | 3 | 7 | left [4,5,6,7] | 0 in [4,7)? no | lo = mid+1=4 |
| 2 | 4 | 6 | 5 | 1 | left [0,1] | 0 in [0,1)? yes | hi = mid-1=4 |
| 3 | 4 | 4 | 4 | 0 | - | nums[mid]==0 | return 4 |
Walk the reasoning. Iteration 1: nums[lo]=4 <= nums[mid]=7, so the left half
[4,5,6,7] is sorted. The target 0 is not in the sorted half’s range
[4, 7), so it cannot be there – discard the left half, set lo = 4.
Iteration 2: now nums[lo]=0 <= nums[mid]=1, so the left half [0, 1] is
sorted (this is just indices 4..5). The target 0 is in [0, 1), so it must
be at or left of mid – set hi = 4. Iteration 3: lo == hi == 4, mid is
also 4, nums[mid] == 0 == target, return.
Dry-run for a miss, nums = [4, 5, 6, 7, 0, 1, 2], target = 3
(expected -1):
| Iter | lo | hi | mid | nums[mid] | sorted half | target in sorted half? | action |
|---|---|---|---|---|---|---|---|
| 1 | 0 | 6 | 3 | 7 | left [4,5,6,7] | 3 in [4,7)? no | lo = mid+1=4 |
| 2 | 4 | 6 | 5 | 1 | left [0,1] | 3 in [0,1)? no | lo = mid+1=6 |
| 3 | 6 | 6 | 6 | 2 | left [2] | 3 in [2,2)? no | lo = mid+1=7 |
| exit | 7 | 6 | - | - | - | lo > hi | return -1 |
Each iteration the target is found not in the sorted half, so we go to the other half until the range empties.
Q1 (apply). Trace nums = [6, 7, 0, 1, 2, 3, 4], target = 6. What is returned?
0 (index of 6)-1, target not found1Q2 (analyze). Why is the test nums[lo] <= nums[mid] written with <=, not <? Think about a two-element range where lo == mid.
lo == mid, so strict < would wrongly call the right half sorted and send the search the wrong way< would work identicallyQ3 (transfer). Why does this algorithm degrade toward O(n) on an array WITH duplicates? What comparison becomes uninformative?
nums[mid] against target before deciding which half is
sorted. On a rotated array that comparison is meaningless – the target could
be on either side of mid. Always classify the half first.nums[lo] < nums[mid] (strict). On a two-element range where lo == mid,
this wrongly concludes “the right half is sorted” and sends the search down
the wrong path. Use <=.nums[mid] in the membership test (target <= nums[mid]). Since
the == target case already returned, including mid in the bound is
harmless on a hit but muddies the reasoning; keep target < nums[mid].nums[lo] <= nums[mid] classification – the worst case becomes O(n)
(LC 81). The distinct-values guarantee is what keeps this O(log n).mid from the membership branch instead of -1 on a miss. The
only place we return an index is the exact-match line; everything else either
narrows the range or, at the end, returns -1.