title: Search in Rotated Sorted Array II slug: search-in-rotated-sorted-array-ii difficulty: medium leetcode_id: 81 leetcode_url: https://leetcode.com/problems/search-in-rotated-sorted-array-ii/ categories: - arrays - binary-search patterns: - slug: binary-search is_optimal: true function_signature: "def search(nums: list[int], target: int) -> bool:" test_cases: visible: - input: { nums: [2, 5, 6, 0, 0, 1, 2], target: 0 } expected: true - input: { nums: [2, 5, 6, 0, 0, 1, 2], target: 3 } expected: false hidden: - input: { nums: [1], target: 1 } expected: true - input: { nums: [1], target: 0 } expected: false - input: { nums: [1, 1, 1, 1, 1], target: 1 } expected: true - input: { nums: [1, 0, 1, 1, 1], target: 0 } expected: true - input: { nums: [1, 3, 1, 1, 1], target: 3 } expected: true description: | There is an integer array `nums` sorted in non-decreasing order (not necessarily with **distinct** values). Before being passed to your function, `nums` is **rotated** at an unknown pivot index `k` (`0 <= k < nums.length`) such that the resulting array is `[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]` (**0-indexed**). For example, `[0,1,2,4,4,4,5,6,6,7]` might be rotated at pivot index `5` and become `[4,5,6,6,7,0,1,2,4,4]`. Given the array `nums` **after** the rotation and an integer `target`, return `true` *if* `target` *is in* `nums`*, or* `false` *if it is not in* `nums`. You must decrease the overall operation steps as much as possible. **Follow up:** This problem is similar to Search in Rotated Sorted Array, but `nums` may contain **duplicates**. Would this affect the runtime complexity? How and why? constraints: | - `1 <= nums.length <= 5000` - `-10^4 <= nums[i] <= 10^4` - `nums` is guaranteed to be rotated at some pivot - `-10^4 <= target <= 10^4` examples: - input: "nums = [2,5,6,0,0,1,2], target = 0" output: "true" explanation: "The target 0 is found in the array." - input: "nums = [2,5,6,0,0,1,2], target = 3" output: "false" explanation: "The target 3 is not in the array." explanation: intuition: | This problem extends Search in Rotated Sorted Array by allowing **duplicate values**. The core insight from the original problem still applies: in a rotated sorted array, at least one half is always sorted, and we can use this to guide our binary search. However, duplicates introduce a tricky edge case. Consider `nums = [1, 0, 1, 1, 1]` with `left = 0`, `mid = 2`, `right = 4`: - `nums[left] = 1`, `nums[mid] = 1`, `nums[right] = 1` - All three values are equal! We can't determine which half is sorted. Think of it like standing at a broken staircase where the step you're on, and the steps at both ends, are all at the same height. You can't tell which direction leads up or down — you have to take a small step to break the tie. The solution is simple: when `nums[left] == nums[mid]`, we can't make a decision, so we **shrink the search space by one** (`left++`). This might degrade to O(n) in the worst case (all duplicates), but it's the best we can do without additional information. approach: | We solve this using **Modified Binary Search with Duplicate Handling**: **Step 1: Initialise pointers** - `left = 0`, `right = len(nums) - 1` - We'll search within `[left, right]` inclusive   **Step 2: Binary search with duplicate handling** - While `left <= right`: - Calculate `mid = left + (right - left) // 2` - If `nums[mid] == target`: return `True` - **Handle duplicates**: If `nums[left] == nums[mid]`: - We can't determine which half is sorted - Increment `left` by 1 and continue - **Determine which half is sorted**: - If `nums[left] < nums[mid]`: **left half is sorted** - If `nums[left] <= target < nums[mid]`: search left half (`right = mid - 1`) - Else: search right half (`left = mid + 1`) - Else: **right half is sorted** - If `nums[mid] < target <= nums[right]`: search right half (`left = mid + 1`) - Else: search left half (`right = mid - 1`)   **Step 3: Return False if not found** - If the loop exits without finding the target, return `False`   The key difference from the no-duplicates version is the duplicate handling step. When we can't determine which half is sorted, we fall back to linear elimination. This ensures correctness while maintaining O(log n) performance for most inputs. common_pitfalls: - title: Ignoring the Duplicate Case description: | The most common mistake is using the exact same logic as Search in Rotated Sorted Array (without duplicates). That algorithm assumes `nums[left] <= nums[mid]` tells us the left half is sorted. With duplicates, `nums[left] == nums[mid]` doesn't give us enough information. For example: - `[1, 0, 1, 1, 1]`: left half contains the rotation point - `[1, 1, 1, 0, 1]`: right half contains the rotation point Both have `nums[left] == nums[mid]`, but the sorted half differs! wrong_approach: "Treating nums[left] == nums[mid] as 'left half sorted'" correct_approach: "When nums[left] == nums[mid], shrink search space with left++" - title: Shrinking Both Ends description: | Some solutions shrink both `left` and `right` when duplicates are found at the boundaries. While this can work, it's more complex and error-prone. A simpler approach is to only increment `left` when `nums[left] == nums[mid]`. This is sufficient to make progress while keeping the logic clean. wrong_approach: "Complex logic to shrink both ends simultaneously" correct_approach: "Simple left++ when nums[left] == nums[mid]" - title: Expecting O(log n) Guarantee description: | Unlike the no-duplicates version which guarantees O(log n), this problem has **O(n) worst case**. Consider `nums = [1, 1, 1, 1, 1]` — we must check every element. This is unavoidable. The problem statement hints at this with "decrease the overall operation steps as much as possible" rather than requiring O(log n). Accept that O(n) worst case is inherent to the problem, not a flaw in your solution. key_takeaways: - "**Duplicates break binary search decisions**: When values at boundaries equal the middle value, you can't determine which half is sorted" - "**Linear fallback is necessary**: Incrementing `left` when stuck ensures progress, even if it degrades to O(n)" - "**Worst case is O(n)**: This is inherent to the problem — no algorithm can do better when all elements are identical" - "**Compare to the original**: Understanding how duplicates change the problem helps solidify your understanding of both problems" time_complexity: "O(log n) average, O(n) worst case. When we can determine the sorted half, we eliminate half the search space. When duplicates prevent this, we fall back to linear elimination." space_complexity: "O(1). Only a constant number of pointer variables are used." solutions: - approach_name: Binary Search with Duplicate Handling is_optimal: true code: | def search(nums: list[int], target: int) -> bool: left, right = 0, len(nums) - 1 while left <= right: mid = left + (right - left) // 2 # Found the target if nums[mid] == target: return True # Handle duplicates: can't determine which half is sorted if nums[left] == nums[mid]: left += 1 continue # Determine which half is sorted if nums[left] < nums[mid]: # Left half is sorted if nums[left] <= target < nums[mid]: # Target is in the sorted left half right = mid - 1 else: # Target is in the right half left = mid + 1 else: # Right half is sorted if nums[mid] < target <= nums[right]: # Target is in the sorted right half left = mid + 1 else: # Target is in the left half right = mid - 1 # Target not found return False explanation: | **Time Complexity:** O(log n) average, O(n) worst case. **Space Complexity:** O(1) — Constant extra space. This solution handles the duplicate case by incrementing `left` when `nums[left] == nums[mid]`. For arrays without many duplicates, it behaves like standard binary search. For arrays with all identical elements, it degrades to linear search — but this is unavoidable. - approach_name: Binary Search with Both-End Shrinking is_optimal: false code: | def search(nums: list[int], target: int) -> bool: left, right = 0, len(nums) - 1 while left <= right: mid = left + (right - left) // 2 if nums[mid] == target: return True # Handle duplicates at both ends if nums[left] == nums[mid] == nums[right]: left += 1 right -= 1 elif nums[left] <= nums[mid]: # Left half is sorted if nums[left] <= target < nums[mid]: right = mid - 1 else: left = mid + 1 else: # Right half is sorted if nums[mid] < target <= nums[right]: left = mid + 1 else: right = mid - 1 return False explanation: | **Time Complexity:** O(log n) average, O(n) worst case. **Space Complexity:** O(1) — Constant extra space. This variant shrinks both ends when `nums[left] == nums[mid] == nums[right]`. It can be slightly faster in practice for arrays with duplicates at both ends, but the logic is more complex. The simpler single-end shrinking is usually preferred. - approach_name: Linear Search is_optimal: false code: | def search(nums: list[int], target: int) -> bool: # Simple membership check return target in nums explanation: | **Time Complexity:** O(n) — Scans the entire array. **Space Complexity:** O(1) — No extra space used. A straightforward linear scan using Python's `in` operator. While this has the same worst-case complexity as the binary search solution, it doesn't take advantage of the sorted structure when duplicates are sparse. Useful as a baseline or for very small arrays.