Find Minimum in Rotated Sorted Array II — Duplicates and the Safe Shrink [LC 154, Google]
Advertisement
Problem Statement
Given a sorted rotated array nums that may contain duplicates, return the minimum element of the array.
Constraints:
n == nums.length1 <= n <= 5000-5000 <= nums[i] <= 5000numsis sorted and rotated between 1 and n times- Duplicates are allowed
Input: nums = [1,3,5]
Output: 1Input: nums = [2,2,2,0,1]
Output: 0Why This Problem Matters
LC 154 is the hard follow-up to LC 153 (Find Minimum in Rotated Sorted Array) and is asked by Google and Microsoft to test whether candidates understand the limits of binary search. In LC 153, all values are distinct, so comparing nums[mid] with nums[hi] always tells you which side the minimum is on. Duplicates break this: when nums[mid] == nums[hi], the minimum could be on either side.
This problem is important not for its algorithm (the fix is one line: hi -= 1) but for the reasoning it requires. Understanding exactly why hi -= 1 is safe — it removes one duplicate without discarding the actual minimum — and why it degrades to O(n) in the worst case demonstrates deep binary search understanding.
The Core Insight
Three cases at each mid:
nums[mid] > nums[hi]: the rotation point is to the right ofmid. Minimum is in right half.lo = mid + 1.nums[mid] < nums[hi]: the minimum is atmidor to its left.hi = mid.nums[mid] == nums[hi]: ambiguous. The minimum could be in either half. Safely shrink byhi -= 1— we knownums[hi]is not the unique minimum (becausenums[mid]is identical, so even ifnums[hi]were the minimum,nums[mid]is an equally good candidate that still lies within the window).
The hi -= 1 operation is safe because it never discards an element that is the only copy of the minimum value.
Visual Dry Run
Input: nums = [2, 2, 2, 0, 1]
| Step | lo | hi | mid | nums[mid] | nums[hi] | Decision |
|---|---|---|---|---|---|---|
| 1 | 0 | 4 | 2 | 2 | 1 | 2 > 1, lo = 3 |
| 2 | 3 | 4 | 3 | 0 | 1 | 0 < 1, hi = 3 |
| 3 | 3 | 3 | — | — | — | return nums[3] = 0 |
Ambiguous case: nums = [3, 1, 3, 3, 3]
| Step | lo | hi | mid | nums[mid] | nums[hi] | Decision |
|---|---|---|---|---|---|---|
| 1 | 0 | 4 | 2 | 3 | 3 | 3 == 3, hi = 3 |
| 2 | 0 | 3 | 1 | 1 | 3 | 1 < 3, hi = 1 |
| 3 | 0 | 1 | 0 | 3 | 1 | 3 > 1, lo = 1 |
| 4 | 1 | 1 | — | — | — | return nums[1] = 1 |
Solution (Optimal)
class Solution:
def findMin(self, nums: list[int]) -> int:
lo, hi = 0, len(nums) - 1
while lo < hi:
mid = lo + (hi - lo) // 2
if nums[mid] > nums[hi]:
# Rotation point is strictly to the right of mid
lo = mid + 1
elif nums[mid] < nums[hi]:
# Minimum is at mid or to the left of mid
hi = mid
else:
# nums[mid] == nums[hi]: ambiguous — safely discard one duplicate from the right
hi -= 1
return nums[lo]var findMin = function(nums) {
let lo = 0, hi = nums.length - 1;
while (lo < hi) {
const mid = lo + Math.floor((hi - lo) / 2);
if (nums[mid] > nums[hi]) {
lo = mid + 1; // rotation point is to the right
} else if (nums[mid] < nums[hi]) {
hi = mid; // minimum is at mid or to the left
} else {
hi--; // nums[mid] == nums[hi]: can safely remove this duplicate
}
}
return nums[lo];
};Time: O(log n) average, O(n) worst case (all identical elements) Space: O(1) — only pointer variables
Common Mistakes
- Using
lo++instead ofhi--in the ambiguous case — both work, buthi--is conventional when comparing tohi. - Using
hi = midin the ambiguous case — this does not make progress whenmid == hi, causing infinite loops. - Forgetting this problem has a different worst case than LC 153 — always communicate the O(n) worst case to interviewers.
- Applying the LC 153 algorithm (two cases) directly — it gives wrong results when duplicates create the ambiguous case.
Interview Tips
- Immediately state: "This is LC 153 with duplicates; the only new case is
nums[mid] == nums[hi], which requireshi--." - Explain WHY
hi--is safe:nums[hi]has a duplicate atnums[mid]that is still inside the window, so discardingnums[hi]cannot lose the minimum. - State the worst-case complexity proactively: O(n) when all elements are equal (e.g.,
[1,1,1,1,1]). - Mention that LC 81 (Search in Rotated Sorted Array II) uses the symmetric
lo++escape for the same reason.
Follow-up Questions
- LC 153 (no duplicates): Two-case solution, guaranteed O(log n).
- LC 81 (search with duplicates): Uses
lo++whennums[lo] == nums[mid]for the symmetric reason. - Can you always do O(log n)? No — the input
[1,1,1,...,1,0,1]requires the search to eventually scan every element in the worst case. - What if you need the index of the minimum, not the value? Return
loinstead ofnums[lo]after the loop. - How does this extend to finding the minimum in a k-rotated array? Same algorithm — rotation count does not change the logic.
Key Takeaways
- LC 154 is the hard variant of LC 153; the only difference is a third case:
nums[mid] == nums[hi], handled byhi -= 1. - The
hi--is safe because whennums[mid] == nums[hi], the minimum cannot be uniquely athi—nums[mid]is an identical value still inside the window. - Worst-case complexity is O(n) for all-equal arrays — this is provably unavoidable and must be disclosed in interviews.
- Use
while lo < hiwithhi = midfor the casenums[mid] < nums[hi]— never usehi = mid - 1here or the minimum atmidgets skipped. - Google and Microsoft ask this problem to verify that candidates can articulate invariant breakdown and worst-case bounds, not just produce code.
- LC 81 uses the same escape-hatch reasoning on the
loside:lo++whennums[lo] == nums[mid]makes the left-sorted check unambiguous. - This problem is a litmus test for depth of understanding: the algorithm is nearly identical to LC 153, but the reasoning is fundamentally different.
Advertisement