Skip to content

Coding patternsBlind 75

Find Minimum in Rotated Sorted Array

Compare the middle with the right end to see which half holds the drop, and throw the other half away. Solved two ways, a linear scan in O(n) and binary search in O(log n), with a step-through, code in five languages and the Big O worked out from the code.

An array of n different numbers was sorted from small to large. Then it was rotated: some numbers were taken off the front and put, in the same order, at the back. For example, [1, 2, 3, 4, 5] rotated twice becomes [3, 4, 5, 1, 2]. It may also have been rotated n times, which gives back the sorted array.

You get the rotated array nums, so n is the length of nums. Return its smallest number. Your code must run in O(log n) time.

Example 1

Input: nums = [6, 8, 9, 11, 2, 4, 5]

Output: 2

The sorted array was [2, 4, 5, 6, 8, 9, 11]. Four numbers moved to the back, so 2 now sits at index 4.

nums
608192113244556

Example 2

Input: nums = [3, 1, 2]

Output: 1

The array [1, 2, 3] was rotated once.

Example 3

Input: nums = [1, 4, 7]

Output: 1

Rotated 3 times, which is a full turn. The array is sorted again, so the first number is the smallest.

  • 1 ≤ n ≤ 5,000
  • −5,000 ≤ nums[i] ≤ 5,000
  • All numbers are different.
  • nums is a sorted array rotated between 1 and n times.
Can numbers repeat?
No. All numbers are different. With repeats, the halving trick can get stuck, which is a harder version of this problem.
Can the array be not rotated at all?
Yes, after n rotations it is sorted again. The answer is then the first number.
Do we return the number or its index?
The number.
Is a simple scan allowed?
It gives the right answer, but the problem asks for O(log n). A scan is O(n).

The array is almost sorted: two sorted runs, with one drop between them. The problem asks for O(log n), which means halving the search space at every step. That is binary search. The question is how to tell, from one middle number, which half holds the drop.

This problem has two approaches. There is no useful step between a full scan and binary search.

Binary search O(log n)

Keep a range low..high that always holds the smallest number. Look at the middle position, middle, and compare nums[middle] with nums[high].

  • If nums[middle] > nums[high], the numbers go down somewhere between middle and high. That drop is where the smallest number sits. So the answer is to the right of middle: set low = middle + 1.
  • Otherwise, middle..high goes up the whole way. Then nums[middle] is the smallest of that part, and the answer is middle itself or to its left: set high = middle. We keep middle, because it may be the answer.

Each step throws away about half the range. When low meets high, one number is left: the smallest.

Why it never misses the answer: each step only drops numbers that are bigger than one we keep, so the smallest number always stays inside low..high.

nums
608192113244556
lowhigh
  1. low, high = 0, len(nums) - 1
  2. while low < high:
  3. middle = (low + high) // 2
  4. if nums[middle] > nums[high]:
  5. low = middle + 1
  6. else:
  7. high = middle
  8. return nums[low]

The smallest number is somewhere in low..high. At the start that is the whole array.

1 / 8
Solution.javasolution.pySolution.cssolution.gosolution.ts
class Solution {
// Binary search: compare the middle with the right end to find the half
// that holds the drop. Each step halves the range, so O(log n) time.
public int findMin(int[] nums) {
int low = 0, high = nums.length - 1;
// low..high always holds the smallest number. Stop when one number is left.
while (low < high) {
int middle = low + (high - low) / 2;
if (nums[middle] > nums[high]) {
// The numbers go down somewhere after middle, and the drop is the minimum.
low = middle + 1;
} else {
// middle..high is sorted, so nums[middle] beats everything to its right.
// Keep middle: it may be the answer.
high = middle;
}
}
return nums[low];
}
}
Time O(log n)Space O(1)Here is how we get these from the code, in four steps.
  1. 1 Find the work that repeats

    Each run of the loop finds the middle, does one comparison and moves one end. That is the same small job every time, so each run is 1 step. There is one loop. The question is how many times it runs.

    1. low, high = 0, len(nums) - 1
    2. while low < high:
    3. middle = (low + high) // 2
    4. if nums[middle] > nums[high]:
    5. low = middle + 1
    6. else:
    7. high = middle
    8. return nums[low]
  2. 2 Count the runs on Example 1

    Example 1 has 7 numbers. The table shows how many numbers are still in low..high before each run. Each run keeps about half of them: 7, then 3, then 2, then 1, and the loop stops.

    3 runs.

    run 1run 2run 3stop
    numbers left7321
  3. 3 Write the count for any n

    Each run halves the range, at least. So after k runs, about n / 2ᵏ numbers are left. The loop stops when 1 is left, so n / 2ᵏ = 1, which means:

    k ≈ log₂ n runs

    Check it with Example 1: log₂ 7 is about 2.8, so 3 runs.

  4. 4 Keep only the part that grows fastest

    There is nothing to drop. log₂ n grows very slowly: doubling n adds only one more run. For the largest input, 5,000 numbers, the loop runs at most 13 times.

    So the time is O(log n).

  5. S Space: count the extra memory the same way

    The code keeps low, high and middle. That is 3 variables, whatever n is, so the space is O(1).

    lowhighmiddle

2,000 random rotated arrays of 1 to 8 different numbers, rotated by any amount. Then every one of the 5,000 rotations of the largest array. The linear scan and the binary search gave the same answer every time.

Largest inputsResult
5,000 numbers (the most allowed), with the minimum in the last place, so the search narrows all the way to the right. Binary search.Under 0.001 s, and under 0.001 MB of extra memory: just three indexes (low, middle and high).