Topics › Binary Search › BS on 1D Arrays
Find minimum in Rotated Sorted Array
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
An array of n distinct integers was sorted in ascending order and then rotated between 1 and n times (rotating n times gives the original array back, so “not rotated” is included). Given the resulting array nums, return its minimum element in O(log n) time.
Constraints: 1 ≤ n ≤ 5000, all values distinct, values fit in a 32-bit signed integer.
Example 1
nums = [3, 4, 5, 1, 2]
answer = 1 // [1, 2, 3, 4, 5] rotated 3 times
Example 2
nums = [4, 5, 6, 7, 0, 1, 2]
answer = 0
Example 3
nums = [11, 13, 15, 17]
answer = 11 // not rotated: the first element
Approach 1: Brute force, scan for the minimum
Intuition. The minimum of any array can be found by looking at every element once. This ignores the rotated-sorted structure entirely, so it is the fallback for arbitrary arrays and the oracle we test the fast versions against.
Algorithm.
best = nums[0].- For each value
v,best = min(best, v). - Return
best.
static int bruteForce(int[] nums) {
int best = nums[0];
for (int v : nums) best = Math.min(best, v);
return best;
}
Complexity. Time O(n), space O(1).
Approach 2: Optimal, take the minimum of the sorted half and discard it
Intuition. A rotated sorted array is two ascending runs; the minimum is the first element of the second run, right after the single “drop”. For any range [lo, hi] with midpoint mid, at least one half is sorted, and we can tell which: if nums[lo] <= nums[mid], the left half [lo, mid] is sorted, otherwise the right half [mid, hi] is.
A sorted half gives up its minimum for free: it is the half’s first element. So at each step we take the sorted half’s first element as a candidate for the answer, and then discard that half, because nothing else in it can be smaller than its first element. The unsorted half must contain the drop, so the true minimum is either the candidate we just recorded or somewhere in the unsorted half, which we keep searching.
A useful shortcut: if the whole current range is sorted (nums[lo] <= nums[hi]), its first element is its minimum and we can stop at once. This is what happens immediately for a non-rotated array.
Algorithm.
lo = 0,hi = n - 1,ans = +infinity.- While
lo <= hi:mid = lo + (hi - lo) / 2.- If
nums[lo] <= nums[hi]:ans = min(ans, nums[lo]), stop. - Else if
nums[lo] <= nums[mid]:ans = min(ans, nums[lo]),lo = mid + 1. - Else:
ans = min(ans, nums[mid]),hi = mid - 1.
- Return
ans.
static int optimal(int[] nums) {
int lo = 0, hi = nums.length - 1, ans = Integer.MAX_VALUE;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (nums[lo] <= nums[hi]) { // whole range sorted: its first element is the smallest
ans = Math.min(ans, nums[lo]);
break;
}
if (nums[lo] <= nums[mid]) { // left half sorted: nums[lo] is its minimum, drop it
ans = Math.min(ans, nums[lo]);
lo = mid + 1;
} else { // right half sorted: nums[mid] is its minimum, drop it
ans = Math.min(ans, nums[mid]);
hi = mid - 1;
}
}
return ans;
}
Dry run on nums = [4, 5, 6, 7, 0, 1, 2]:
| lo | hi | mid | nums[lo] <= nums[hi]? | sorted half | candidate | action |
|---|---|---|---|---|---|---|
| 0 | 6 | 3 | 4 <= 2? no | left (4 <= 7) | ans = 4 | lo = 4 |
| 4 | 6 | 5 | 0 <= 2? yes | whole range | ans = min(4, 0) = 0 | stop |
Complexity. Time O(log n): each iteration discards about half of the range. Space O(1).
Two remarks on ans. It starts at Integer.MAX_VALUE, which is safe even if the array’s minimum is Integer.MAX_VALUE (a one-element array), because min(MAX_VALUE, MAX_VALUE) is still the right answer. And every value assigned to ans is a real element of the array, so the method never invents a value.
Approach 3: Optimal, compare mid with the right end
Intuition. There is a tighter formulation that needs no ans variable. Compare nums[mid] with nums[hi]:
- If
nums[mid] > nums[hi], the drop lies strictly to the right ofmid(a sorted range could not have a larger value before a smaller one), so the minimum is in[mid + 1, hi]. - Otherwise
nums[mid] < nums[hi](distinct values), so[mid, hi]is ascending and the minimum of the whole range is atmidor to its left: shrink to[lo, mid].
The invariant “the minimum is in [lo, hi]” holds throughout, the range strictly shrinks each step, and when lo == hi that single index must be the minimum.
Why compare with nums[hi] rather than nums[lo]? Comparing with the left end is ambiguous: for a non-rotated array nums[mid] > nums[lo] and the minimum is on the left, but for [5, 6, 7, 1, 2] also nums[mid] > nums[lo] while the minimum is on the right. The right end does not have this problem because the minimum always sits in the run that ends at hi.
Algorithm.
lo = 0,hi = n - 1.- While
lo < hi:mid = lo + (hi - lo) / 2; ifnums[mid] > nums[hi],lo = mid + 1; elsehi = mid. - Return
nums[lo].
static int optimalCompareRight(int[] nums) {
int lo = 0, hi = nums.length - 1;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (nums[mid] > nums[hi]) lo = mid + 1; // the drop is strictly right of mid
else hi = mid; // nums[mid] < nums[hi]: minimum is mid or left of it
}
return nums[lo];
}
Dry run on nums = [3, 4, 5, 1, 2]:
| lo | hi | mid | nums[mid] | nums[hi] | action |
|---|---|---|---|---|---|
| 0 | 4 | 2 | 5 | 2 | 5 > 2: lo = 3 |
| 3 | 4 | 3 | 1 | 2 | 1 < 2: hi = 3 |
| 3 | 3 | lo == hi: return nums[3] = 1 |
Complexity. Time O(log n), space O(1).
Note the asymmetry lo = mid + 1 versus hi = mid: when nums[mid] > nums[hi], mid is definitely not the minimum, so it is excluded; when nums[mid] < nums[hi], mid might be the minimum, so it must stay. Because mid < hi whenever lo < hi, setting hi = mid always shrinks the range and the loop terminates.
Edge cases and pitfalls
- Not rotated.
nums[lo] <= nums[hi]on the first check, so Approach 2 stops immediately; Approach 3 keeps settinghi = miduntillo == hi == 0. - Rotated
n - 1times. The minimum is the last element, for example[2, 3, 4, 5, 1]. Tested together with “rotated once”. - Single element.
lo == hifrom the start; Approach 3 skips the loop, Approach 2 takes the “whole range sorted” branch. - Two elements.
[2, 1]and[1, 2]check thatmid = lois handled. - Extreme values.
[Integer.MAX_VALUE]must returnInteger.MAX_VALUE; a test confirms that the initialansdoes not mask it. Values are only compared, never added, so no overflow is possible there. - Comparing with
nums[lo]in Approach 3. This is the classic mistake and gives wrong answers on non-rotated inputs; compare withnums[hi]. - Duplicates. Both optimal methods assume distinct values. With duplicates
nums[mid] == nums[hi]makes the decision ambiguous and requires a shrink step, as in the duplicates version of the rotated search. - Overflow in
mid. Uselo + (hi - lo) / 2.
Related problems
- Find out how many times the array is rotated is this problem with the index of the minimum returned instead of its value.
- Search in rotated sorted array-I uses the same “which half is sorted” test to locate an arbitrary target.
- Search in rotated sorted array-II shows what to do when duplicates break the test.