Search in Rotated Sorted Array II
Description
You are given an integer array nums that was originally sorted in non-decreasing order. The array may contain duplicate values. Before being passed to your function, nums was rotated at an unknown pivot index k (where 0 ≤ k < nums.length). After the rotation, the array becomes:
[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]
For example, the sorted array [0,1,2,4,4,4,5,6,6,7] might be rotated at pivot index 5 to become [4,5,6,6,7,0,1,2,4,4].
Given this rotated array nums and an integer target, return true if target exists in nums, or false if it does not.
You should try to minimize the number of operations as much as possible.
Important: Unlike the classic "Search in Rotated Sorted Array" problem (where all elements are distinct), this version allows duplicates. This seemingly small difference has a significant impact on how efficiently we can search.
Examples
Example 1
Input: nums = [2,5,6,0,0,1,2], target = 0
Output: true
Explanation: The value 0 appears in the array at index 3 (and also at index 4). Since the target exists in the array, we return true.
Example 2
Input: nums = [2,5,6,0,0,1,2], target = 3
Output: false
Explanation: The value 3 does not appear anywhere in the array. We return false.
Constraints
- 1 ≤ nums.length ≤ 5000
- -10^4 ≤ nums[i] ≤ 10^4
numsis guaranteed to be rotated at some pivot- -10^4 ≤ target ≤ 10^4
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?
Editorial
Brute Force - Linear Scan
Intuition
The simplest way to check if a value exists in an array is to look at every single element, one by one, from left to right. If we find the target, we return true. If we reach the end without finding it, we return false.
This approach completely ignores the fact that the array is sorted (even if rotated). It treats the input as an ordinary, unordered list of numbers. Think of it like searching for a specific book on a shelf by checking every book from left to right — you will definitely find it if it is there, but it might take a while if the shelf is long.
While this approach always works, it does not take advantage of any structure in the data. For an array of 5000 elements, we might end up checking all 5000 of them in the worst case.
Step-by-Step Explanation
Let's trace through nums = [2, 5, 6, 0, 0, 1, 2], target = 0.
Step 1: Start at index 0. The element is 2. Is 2 == 0? No. Move to the next element.
Step 2: Index 1. The element is 5. Is 5 == 0? No. Move to the next element.
Step 3: Index 2. The element is 6. Is 6 == 0? No. Move to the next element.
Step 4: Index 3. The element is 0. Is 0 == 0? Yes! We found the target.
Return true.
We had to check 4 elements before finding the target. In the worst case (target not in array, or target at the very end), we would check all 7 elements.
Linear Scan — Check Every Element — Watch as we scan the array from left to right, comparing each element against the target value 0.
Algorithm
- Iterate through the array from index 0 to n-1.
- At each index
i, comparenums[i]withtarget. - If
nums[i] == target, returntrueimmediately. - If the loop completes without finding the target, return
false.
Code
class Solution {
public:
bool search(vector<int>& nums, int target) {
for (int i = 0; i < nums.size(); i++) {
if (nums[i] == target) {
return true;
}
}
return false;
}
};class Solution:
def search(self, nums: list[int], target: int) -> bool:
for num in nums:
if num == target:
return True
return Falseclass Solution {
public boolean search(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
if (nums[i] == target) {
return true;
}
}
return false;
}
}Complexity Analysis
Time Complexity: O(n)
We visit each element at most once, performing a constant-time comparison at each step. In the worst case (target is the last element or not present), we scan the entire array of n elements.
Space Complexity: O(1)
We only use a single loop variable — no extra data structures are needed regardless of the input size.
Why This Approach Is Not Efficient
The linear scan completely ignores a valuable property of the input: the array was sorted before being rotated. A rotated sorted array is made up of two sorted halves. For example, [2, 5, 6, 0, 0, 1, 2] consists of the sorted segment [2, 5, 6] followed by the sorted segment [0, 0, 1, 2].
When an array is sorted, we can use binary search to find an element in O(log n) time instead of O(n). Even though the array is rotated, we can adapt binary search to work here. At each step of binary search, at least one of the two halves (left or right of mid) must be properly sorted. We can check if the target falls within that sorted half and narrow our search accordingly.
The complication with duplicates is that when nums[left] == nums[mid] == nums[right], we cannot determine which half is sorted. In that specific scenario, we must fall back to shrinking the search space by one element from each end. This means the worst case is still O(n) (for example, an array like [1,1,1,1,1,2,1,1,1]), but for most practical inputs with distinct or mostly-distinct values, binary search achieves O(log n) — a significant improvement over always scanning every element.
The key takeaway: while both approaches share the same O(n) worst case, binary search is dramatically faster on average because it skips large portions of the array whenever it can identify a sorted half.
Optimal Approach - Binary Search with Duplicate Handling
Intuition
A rotated sorted array has a special structure: it is composed of two sorted segments joined at the rotation point. For example, [2, 5, 6, | 0, 0, 1, 2] has a left sorted segment [2, 5, 6] and a right sorted segment [0, 0, 1, 2].
In standard binary search on a rotated sorted array (without duplicates), here is the core idea:
- Compute
mid = (left + right) / 2. - Determine which half (
left..midormid..right) is sorted. - Check if the target falls within that sorted range.
- If yes, search that half. If no, search the other half.
We can tell which half is sorted by comparing nums[left] with nums[mid]:
- If
nums[left] <= nums[mid], the left half is sorted. - Otherwise, the right half is sorted.
The duplicate problem: When nums[left] == nums[mid] == nums[right], we cannot tell which half is sorted. Consider these two arrays:
[1, 1, 1, 1, 2, 1]— the right half contains the "break"[1, 2, 1, 1, 1, 1]— the left half contains the "break"
Both have nums[left] = nums[mid] = nums[right] = 1, but the sorted half is different! The solution is straightforward: when this ambiguous case occurs, we simply shrink the boundaries by incrementing left and decrementing right. This removes duplicate elements from the edges without losing any information (since we already checked nums[mid] against the target). In the worst case (all elements identical), this degrades to O(n), but on typical inputs with varied values, we still achieve O(log n).

Step-by-Step Explanation
Let's trace through nums = [2, 5, 6, 0, 0, 1, 2], target = 1 to see several iterations of binary search including the sorted-half decision logic.
Iteration 1: left=0, right=6, mid=(0+6)/2=3.
- nums[mid] = nums[3] = 0. Is 0 == 1? No.
- Check for duplicate ambiguity: nums[left]=2, nums[mid]=0, nums[right]=2. All three equal? No (2 ≠ 0). No ambiguity.
- Is the left half sorted? nums[left]=2 ≤ nums[mid]=0? No (2 > 0). So the left half is NOT sorted, meaning the right half [0,0,1,2] must be sorted.
- Is target=1 in the sorted right half? Check: nums[mid] < 1 ≤ nums[right]? 0 < 1 and 1 ≤ 2? Yes! Search right: set left = mid + 1 = 4.
Iteration 2: left=4, right=6, mid=(4+6)/2=5.
- nums[mid] = nums[5] = 1. Is 1 == 1? YES! Target found!
Return true.
Binary search found the target in just 2 iterations, compared to 6 comparisons a linear scan would need to reach index 5.
Now let's also trace nums = [2, 5, 6, 0, 0, 1, 2], target = 3 (a case where the target does not exist) to see more iterations:
Iteration 1: left=0, right=6, mid=3. nums[3]=0 ≠ 3.
- Left sorted? 2 ≤ 0? No → right half is sorted.
- Target=3 in right half? 0 < 3 ≤ 2? No (3 > 2). Target is NOT in the sorted right half.
- Search left: right = mid - 1 = 2.
Iteration 2: left=0, right=2, mid=1. nums[1]=5 ≠ 3.
- Left sorted? nums[0]=2 ≤ nums[1]=5? Yes → left half [2,5] is sorted.
- Target=3 in left half? 2 ≤ 3 < 5? Yes! Search left: right = mid - 1 = 0.
Iteration 3: left=0, right=0, mid=0. nums[0]=2 ≠ 3.
- Left sorted? 2 ≤ 2? Yes. Target=3 in [2..2]? 2 ≤ 3 < 2? No. Search right: left = 1.
Now left=1 > right=0. Loop ends. Return false.
Binary Search with Duplicate Handling — Finding Target in Rotated Sorted Array — Watch how binary search identifies the sorted half at each step and narrows the search space, with special handling when duplicates create ambiguity.
Algorithm
- Initialize
left = 0andright = nums.length - 1. - While
left ≤ right:
a. Computemid = left + (right - left) / 2.
b. Ifnums[mid] == target, returntrue.
c. Handle duplicates: Ifnums[left] == nums[mid]andnums[mid] == nums[right], we cannot determine which half is sorted. Incrementleftby 1 and decrementrightby 1, then continue to the next iteration.
d. Left half is sorted (ifnums[left] ≤ nums[mid]):- If
nums[left] ≤ target < nums[mid], the target is in the left half → setright = mid - 1. - Otherwise, search the right half → set
left = mid + 1.
e. Right half is sorted (otherwise): - If
nums[mid] < target ≤ nums[right], the target is in the right half → setleft = mid + 1. - Otherwise, search the left half → set
right = mid - 1.
- If
- If the loop ends without finding the target, return
false.
Code
class Solution {
public:
bool search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return true;
}
// Handle the ambiguous duplicate case
if (nums[left] == nums[mid] && nums[mid] == nums[right]) {
left++;
right--;
continue;
}
// Left half is sorted
if (nums[left] <= nums[mid]) {
if (nums[left] <= target && target < nums[mid]) {
right = mid - 1; // target is in sorted left half
} else {
left = mid + 1; // target is in right half
}
}
// Right half is sorted
else {
if (nums[mid] < target && target <= nums[right]) {
left = mid + 1; // target is in sorted right half
} else {
right = mid - 1; // target is in left half
}
}
}
return false;
}
};class Solution:
def search(self, nums: list[int], target: int) -> bool:
left = 0
right = len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return True
# Handle the ambiguous duplicate case
if nums[left] == nums[mid] == nums[right]:
left += 1
right -= 1
continue
# Left half is sorted
if nums[left] <= nums[mid]:
if nums[left] <= target < nums[mid]:
right = mid - 1 # target is in sorted left half
else:
left = mid + 1 # target is in right half
# Right half is sorted
else:
if nums[mid] < target <= nums[right]:
left = mid + 1 # target is in sorted right half
else:
right = mid - 1 # target is in left half
return Falseclass Solution {
public boolean search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return true;
}
// Handle the ambiguous duplicate case
if (nums[left] == nums[mid] && nums[mid] == nums[right]) {
left++;
right--;
continue;
}
// Left half is sorted
if (nums[left] <= nums[mid]) {
if (nums[left] <= target && target < nums[mid]) {
right = mid - 1; // target is in sorted left half
} else {
left = mid + 1; // target is in right half
}
}
// Right half is sorted
else {
if (nums[mid] < target && target <= nums[right]) {
left = mid + 1; // target is in sorted right half
} else {
right = mid - 1; // target is in left half
}
}
}
return false;
}
}Complexity Analysis
Time Complexity: O(log n) average, O(n) worst case
In the average case, binary search eliminates half the search space at each iteration, giving O(log n) time. However, when the array contains many duplicates and we hit the ambiguous case (nums[left] == nums[mid] == nums[right]), we can only shrink the search space by 2 elements (one from each end). In the worst case — for example, nums = [1,1,1,1,1,2,1,1,1] with target=2 — we might shrink one element at a time through most of the array, resulting in O(n) time. This worst case is unavoidable: with duplicates, there is no way to determine which half contains the pivot without checking.
Space Complexity: O(1)
We use only a fixed number of variables (left, right, mid) regardless of the input size. No recursion or auxiliary data structures are needed.
Comparison with Brute Force:
| Approach | Average Case | Worst Case | Space |
|---|---|---|---|
| Linear Scan | O(n) | O(n) | O(1) |
| Binary Search (with duplicates) | O(log n) | O(n) | O(1) |
While the worst case is the same, the average case of binary search is exponentially faster. For an array of 5000 elements, binary search typically needs about 12-13 comparisons versus up to 5000 for linear scan.