185 lines
7.5 KiB
YAML
185 lines
7.5 KiB
YAML
title: Find Minimum in Rotated Sorted Array
|
|
slug: find-minimum-in-rotated-sorted-array
|
|
difficulty: medium
|
|
leetcode_id: 153
|
|
leetcode_url: https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/
|
|
categories:
|
|
- arrays
|
|
- binary-search
|
|
patterns:
|
|
- binary-search
|
|
|
|
function_signature: "def find_min(nums: list[int]) -> int:"
|
|
|
|
test_cases:
|
|
visible:
|
|
- input: { nums: [3, 4, 5, 1, 2] }
|
|
expected: 1
|
|
- input: { nums: [4, 5, 6, 7, 0, 1, 2] }
|
|
expected: 0
|
|
- input: { nums: [11, 13, 15, 17] }
|
|
expected: 11
|
|
hidden:
|
|
- input: { nums: [1] }
|
|
expected: 1
|
|
- input: { nums: [2, 1] }
|
|
expected: 1
|
|
- input: { nums: [1, 2, 3, 4, 5] }
|
|
expected: 1
|
|
- input: { nums: [5, 1, 2, 3, 4] }
|
|
expected: 1
|
|
|
|
description: |
|
|
Suppose an array of length `n` sorted in ascending order is **rotated** between `1` and `n` times. For example, the array `nums = [0,1,2,4,5,6,7]` might become:
|
|
|
|
- `[4,5,6,7,0,1,2]` if it was rotated 4 times
|
|
- `[0,1,2,4,5,6,7]` if it was rotated 7 times (back to original)
|
|
|
|
Given the sorted rotated array `nums` of **unique** elements, return *the minimum element of this array*.
|
|
|
|
You must write an algorithm that runs in **O(log n)** time.
|
|
|
|
constraints: |
|
|
- `n == nums.length`
|
|
- `1 <= n <= 5000`
|
|
- `-5000 <= nums[i] <= 5000`
|
|
- All the integers of `nums` are **unique**
|
|
- `nums` is sorted and rotated between 1 and n times
|
|
|
|
examples:
|
|
- input: "nums = [3,4,5,1,2]"
|
|
output: "1"
|
|
explanation: "Original array was [1,2,3,4,5] rotated 3 times."
|
|
- input: "nums = [4,5,6,7,0,1,2]"
|
|
output: "0"
|
|
explanation: "Original array was [0,1,2,4,5,6,7] rotated 4 times."
|
|
- input: "nums = [11,13,15,17]"
|
|
output: "11"
|
|
explanation: "Array was rotated 4 times (full rotation), so minimum is first element."
|
|
|
|
explanation:
|
|
intuition: |
|
|
Visualise a rotated sorted array: it's like taking a sorted array, cutting it somewhere, and swapping the two pieces. This creates a **pivot point** — the place where the large values suddenly drop to small values.
|
|
|
|
For example, in `[4,5,6,7,0,1,2]`, the pivot is between 7 and 0. The minimum element is always at this pivot point!
|
|
|
|
Think of it like this: the array has two sorted "halves". One half has larger values, the other has smaller values. The minimum is the first element of the smaller half.
|
|
|
|
How do we find it with binary search? Compare `nums[mid]` with `nums[right]`:
|
|
- If `nums[mid] > nums[right]`: We're in the "larger" half. The pivot (minimum) must be to the right.
|
|
- If `nums[mid] <= nums[right]`: We're in the "smaller" half or at the minimum. The pivot is at `mid` or to the left.
|
|
|
|
Why compare with `right` instead of `left`? Because comparing with `right` consistently tells us which "half" we're in, regardless of how much the array was rotated.
|
|
|
|
approach: |
|
|
We solve this using **Modified Binary Search**:
|
|
|
|
**Step 1: Initialise pointers**
|
|
|
|
- `left = 0`, `right = len(nums) - 1`
|
|
- The minimum must be somewhere in `[left, right]`
|
|
|
|
|
|
|
|
**Step 2: Binary search with right comparison**
|
|
|
|
- While `left < right`:
|
|
- Calculate `mid = left + (right - left) // 2`
|
|
- If `nums[mid] > nums[right]`:
|
|
- The pivot (minimum) is in the right half
|
|
- Set `left = mid + 1` (exclude mid — it's too large)
|
|
- Else (`nums[mid] <= nums[right]`):
|
|
- The pivot is at `mid` or in the left half
|
|
- Set `right = mid` (keep mid in consideration)
|
|
|
|
|
|
|
|
**Step 3: Return the minimum**
|
|
|
|
- When `left == right`, we've found the minimum
|
|
- Return `nums[left]`
|
|
|
|
|
|
|
|
This works because we're essentially searching for the "boundary" where the array transitions from large values to small values.
|
|
|
|
common_pitfalls:
|
|
- title: Comparing with Left Instead of Right
|
|
description: |
|
|
Comparing `nums[mid]` with `nums[left]` doesn't work consistently. Consider `[2, 1]`:
|
|
- `mid = 0`, `nums[mid] = 2`, `nums[left] = 2`
|
|
- `nums[mid] > nums[left]` is false, but the minimum is on the right!
|
|
|
|
Comparing with `nums[right]` works because the right element is always either in the "smaller half" (after pivot) or the array isn't rotated.
|
|
wrong_approach: "if nums[mid] > nums[left]: search right"
|
|
correct_approach: "if nums[mid] > nums[right]: search right"
|
|
|
|
- title: Using left <= right Loop Condition
|
|
description: |
|
|
For this problem, use `while left < right`. When `left == right`, we've found the answer. Using `<=` can cause infinite loops because we're not always excluding `mid`.
|
|
wrong_approach: "while left <= right"
|
|
correct_approach: "while left < right"
|
|
|
|
- title: Excluding mid Incorrectly
|
|
description: |
|
|
When `nums[mid] <= nums[right]`, `mid` could be the minimum! We must keep it in consideration by setting `right = mid`, not `right = mid - 1`.
|
|
|
|
When `nums[mid] > nums[right]`, we know `mid` is definitely not the minimum (it's larger than something to its right), so `left = mid + 1` is safe.
|
|
wrong_approach: "right = mid - 1 when nums[mid] <= nums[right]"
|
|
correct_approach: "right = mid (keep mid as a candidate)"
|
|
|
|
key_takeaways:
|
|
- "**Binary search on rotated arrays**: Compare with the right boundary to determine which half contains the answer"
|
|
- "**Understanding the structure**: A rotated sorted array has two sorted segments — find the boundary between them"
|
|
- "**Careful with boundary updates**: `mid + 1` vs `mid` depends on whether mid can be the answer"
|
|
- "**Foundation for harder problems**: This technique extends to searching for any element in rotated arrays"
|
|
|
|
time_complexity: "O(log n). Each iteration halves the search space."
|
|
space_complexity: "O(1). Only a constant number of variables are used."
|
|
|
|
solutions:
|
|
- approach_name: Binary Search
|
|
is_optimal: true
|
|
code: |
|
|
def find_min(nums: list[int]) -> int:
|
|
left, right = 0, len(nums) - 1
|
|
|
|
while left < right:
|
|
mid = left + (right - left) // 2
|
|
|
|
if nums[mid] > nums[right]:
|
|
# Mid is in the "larger" half
|
|
# Minimum must be to the right of mid
|
|
left = mid + 1
|
|
else:
|
|
# Mid is in the "smaller" half (or at the minimum)
|
|
# Minimum is at mid or to the left
|
|
right = mid
|
|
|
|
# left == right, pointing to the minimum
|
|
return nums[left]
|
|
explanation: |
|
|
**Time Complexity:** O(log n) — Search space halves each iteration.
|
|
|
|
**Space Complexity:** O(1) — Constant extra space.
|
|
|
|
We compare `nums[mid]` with `nums[right]` to determine which half contains the minimum. If `mid > right`, the pivot is on the right. Otherwise, it's at `mid` or on the left. The loop converges to the exact position of the minimum.
|
|
|
|
- approach_name: Linear Scan
|
|
is_optimal: false
|
|
code: |
|
|
def find_min(nums: list[int]) -> int:
|
|
# Find where sorted order breaks
|
|
for i in range(1, len(nums)):
|
|
if nums[i] < nums[i - 1]:
|
|
return nums[i]
|
|
|
|
# No break found — array wasn't rotated (or rotated fully)
|
|
return nums[0]
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Scans through the array.
|
|
|
|
**Space Complexity:** O(1) — Constant extra space.
|
|
|
|
Find the point where the sorted order breaks (current element less than previous). The element at that point is the minimum. If no break is found, the array wasn't rotated, so return the first element. This doesn't meet the O(log n) requirement but is useful for understanding the problem.
|