81. Search in Rotated Sorted Array II
Problem Description
You are given an integer array nums
that was originally sorted in non-decreasing order (may contain duplicate values). This array has been rotated at some unknown pivot index k
(where 0 <= k < nums.length
).
The rotation works as follows: if the original array is [a, b, c, d, e, f]
and it's rotated at pivot index k = 3
, the resulting array becomes [d, e, f, a, b, c]
. In other words, the array is split at position k
, and the second part is moved to the front.
For example:
- Original sorted array:
[0,1,2,4,4,4,5,6,6,7]
- After rotation at pivot index 5:
[4,5,6,6,7,0,1,2,4,4]
Given the rotated array nums
and an integer target
, you need to determine if target
exists in the array. Return true
if it exists, false
otherwise.
The challenge is to implement this search efficiently, minimizing the number of operations. Since the array was originally sorted (though now rotated), you should leverage this property to achieve better than linear time complexity when possible.
Note that the presence of duplicates makes this problem more complex than searching in a rotated sorted array without duplicates, as it becomes harder to determine which portion of the array is properly sorted during binary search.
Intuition
Since the array was originally sorted and then rotated, we can still use binary search, but we need to handle the rotation carefully. The key insight is that at any point when we split the array in half, at least one half must be properly sorted.
Let's think about what happens when we pick a middle element. We have three cases based on comparing nums[mid]
with nums[r]
:
-
When
nums[mid] > nums[r]
: This means the rotation point is somewhere in the right half. Therefore, the left half[l, mid]
must be sorted. For example, if we have[4,5,6,7,0,1,2]
andmid
points to 7, we know[4,5,6,7]
is sorted because 7 > 2. -
When
nums[mid] < nums[r]
: This means the rotation point is in the left half or doesn't exist in our current range. Therefore, the right half[mid+1, r]
must be sorted. For example, if we have[6,7,0,1,2,3,4]
andmid
points to 1, we know[1,2,3,4]
is sorted because 1 < 4. -
When
nums[mid] == nums[r]
: This is the tricky case that arises due to duplicates. We can't determine which half is sorted. For instance, with[1,0,1,1,1]
or[1,1,1,0,1]
, whenmid
andr
both point to elements with value 1, we can't tell where the rotation point is.
Once we identify which half is sorted, we can easily check if the target lies in that sorted half by comparing it with the boundary values. If the target is in the sorted half, we search there; otherwise, we search the other half.
The special case of nums[mid] == nums[r]
requires careful handling. Since we can't determine which side to search, the safest approach is to simply shrink the search space by reducing r
by 1. This ensures we don't miss the target, though it may degrade to O(n) complexity in the worst case when many duplicates exist.
This approach maintains the binary search structure while adapting to handle both the rotation and the presence of duplicates.
Learn more about Binary Search patterns.
Solution Approach
We implement a modified binary search algorithm that handles both the rotation and duplicate values. Let's walk through the implementation step by step:
Initialization:
- Set left boundary
l = 0
and right boundaryr = n - 1
, wheren
is the length of the array - We'll search while
l < r
to narrow down to a single element
Binary Search Loop:
In each iteration, we calculate the midpoint: mid = (l + r) >> 1
(using bit shift for division by 2).
Then we handle three distinct cases:
Case 1: nums[mid] > nums[r]
- This indicates the left portion
[l, mid]
is sorted (rotation point is in the right half) - Check if target falls within the sorted left portion:
nums[l] <= target <= nums[mid]
- If yes: Search in left half by setting
r = mid
- If no: Search in right half by setting
l = mid + 1
- If yes: Search in left half by setting
Case 2: nums[mid] < nums[r]
- This indicates the right portion
[mid+1, r]
is sorted (rotation point is in the left half or doesn't exist) - Check if target falls within the sorted right portion:
nums[mid] < target <= nums[r]
- If yes: Search in right half by setting
l = mid + 1
- If no: Search in left half by setting
r = mid
- If yes: Search in right half by setting
Case 3: nums[mid] == nums[r]
- We cannot determine which portion is sorted due to duplicates
- Safely reduce the search space by decrementing
r
by 1:r -= 1
- This ensures we don't miss the target, though it may degrade performance with many duplicates
Final Check:
After the loop terminates (when l == r
), we check if nums[l] == target
to determine if the target exists in the array.
Key Implementation Details:
- When the target is in the sorted portion, we use inclusive comparisons to ensure we don't miss boundary values
- In Case 1, we use
r = mid
instead ofr = mid - 1
becausemid
could be the target - In Case 2, we use
l = mid + 1
because we've already confirmednums[mid] != target
(sincenums[mid] < target
) - The handling of duplicates (Case 3) is crucial for correctness but may cause O(n) worst-case complexity
This approach maintains O(log n) average time complexity for most inputs while correctly handling the edge cases introduced by duplicates.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through the algorithm with the array nums = [4,5,6,6,7,0,1,2,4,4]
and target = 1
.
Initial State:
- Array:
[4,5,6,6,7,0,1,2,4,4]
l = 0
,r = 9
,target = 1
Iteration 1:
mid = (0 + 9) >> 1 = 4
nums[mid] = 7
,nums[r] = 4
- Since
7 > 4
(Case 1), the left portion[4,5,6,6,7]
is sorted - Check if target
1
is in sorted left:4 <= 1 <= 7
? No - Target must be in right half, so
l = mid + 1 = 5
- New range:
[0,1,2,4,4]
with indices 5-9
Iteration 2:
l = 5
,r = 9
mid = (5 + 9) >> 1 = 7
nums[mid] = 2
,nums[r] = 4
- Since
2 < 4
(Case 2), the right portion[2,4,4]
is sorted - Check if target
1
is in sorted right:2 < 1 <= 4
? No - Target must be in left half, so
r = mid = 7
- New range:
[0,1,2]
with indices 5-7
Iteration 3:
l = 5
,r = 7
mid = (5 + 7) >> 1 = 6
nums[mid] = 1
,nums[r] = 2
- Since
1 < 2
(Case 2), the right portion[1,2]
is sorted - Check if target
1
is in sorted right:1 < 1 <= 2
? No (since1
is not less than1
) - Target must be in left half (which includes mid), so
r = mid = 6
- New range:
[0,1]
with indices 5-6
Iteration 4:
l = 5
,r = 6
mid = (5 + 6) >> 1 = 5
nums[mid] = 0
,nums[r] = 1
- Since
0 < 1
(Case 2), the right portion is sorted - Check if target
1
is in sorted right:0 < 1 <= 1
? Yes - Search in right half, so
l = mid + 1 = 6
- New range: single element at index 6
Final Check:
l = r = 6
nums[6] = 1
, which equals our target- Return
true
The algorithm successfully found the target by intelligently navigating through the rotated array, using the sorted properties of each half to eliminate portions of the search space.
Solution Implementation
1class Solution:
2 def search(self, nums: List[int], target: int) -> bool:
3 """
4 Search for target in a rotated sorted array that may contain duplicates.
5 Uses modified binary search to handle rotation and duplicates.
6
7 Args:
8 nums: Rotated sorted array possibly with duplicates
9 target: Value to search for
10
11 Returns:
12 True if target exists in array, False otherwise
13 """
14 n = len(nums)
15 left, right = 0, n - 1
16
17 while left < right:
18 # Calculate middle index
19 mid = (left + right) // 2
20
21 # Case 1: Left half is sorted (mid value > right value)
22 if nums[mid] > nums[right]:
23 # Check if target is in the sorted left half
24 if nums[left] <= target <= nums[mid]:
25 # Target is in left half, search there
26 right = mid
27 else:
28 # Target must be in right half
29 left = mid + 1
30
31 # Case 2: Right half is sorted (mid value < right value)
32 elif nums[mid] < nums[right]:
33 # Check if target is in the sorted right half
34 if nums[mid] < target <= nums[right]:
35 # Target is in right half, search there
36 left = mid + 1
37 else:
38 # Target must be in left half
39 right = mid
40
41 # Case 3: Cannot determine which half is sorted due to duplicates
42 # (nums[mid] == nums[right])
43 else:
44 # Reduce search space by moving right pointer
45 right -= 1
46
47 # Check if the remaining element is the target
48 return nums[left] == target
49
1class Solution {
2 /**
3 * Search for a target value in a rotated sorted array that may contain duplicates.
4 * Uses modified binary search to handle the rotation and duplicates.
5 *
6 * @param nums the rotated sorted array with possible duplicates
7 * @param target the value to search for
8 * @return true if target exists in the array, false otherwise
9 */
10 public boolean search(int[] nums, int target) {
11 int left = 0;
12 int right = nums.length - 1;
13
14 while (left < right) {
15 int mid = (left + right) >> 1; // Equivalent to (left + right) / 2
16
17 // Case 1: Left half is sorted (mid is in the left sorted portion)
18 if (nums[mid] > nums[right]) {
19 // Check if target is within the sorted left half
20 if (nums[left] <= target && target <= nums[mid]) {
21 right = mid; // Target must be in left half, inclusive of mid
22 } else {
23 left = mid + 1; // Target must be in right half
24 }
25 }
26 // Case 2: Right half is sorted (mid is in the right sorted portion)
27 else if (nums[mid] < nums[right]) {
28 // Check if target is within the sorted right half
29 if (nums[mid] < target && target <= nums[right]) {
30 left = mid + 1; // Target must be in right half
31 } else {
32 right = mid; // Target must be in left half, inclusive of mid
33 }
34 }
35 // Case 3: Cannot determine which half is sorted due to duplicates
36 else {
37 // nums[mid] == nums[right], shrink search space by moving right pointer
38 right--;
39 }
40 }
41
42 // Check if the remaining element is the target
43 return nums[left] == target;
44 }
45}
46
1class Solution {
2public:
3 bool search(vector<int>& nums, int target) {
4 int left = 0;
5 int right = nums.size() - 1;
6
7 while (left < right) {
8 int mid = left + (right - left) / 2; // Calculate middle index to avoid overflow
9
10 // Case 1: Left half is sorted (pivot is in right half)
11 if (nums[mid] > nums[right]) {
12 // Check if target is in the sorted left half
13 if (nums[left] <= target && target <= nums[mid]) {
14 right = mid; // Search in left half
15 } else {
16 left = mid + 1; // Search in right half
17 }
18 }
19 // Case 2: Right half is sorted (pivot is in left half)
20 else if (nums[mid] < nums[right]) {
21 // Check if target is in the sorted right half
22 if (nums[mid] < target && target <= nums[right]) {
23 left = mid + 1; // Search in right half
24 } else {
25 right = mid; // Search in left half
26 }
27 }
28 // Case 3: Cannot determine which half is sorted due to duplicates
29 else {
30 // nums[mid] == nums[right], reduce search space by one
31 --right;
32 }
33 }
34
35 // Check if the remaining element is the target
36 return nums[left] == target;
37 }
38};
39
1/**
2 * Searches for a target value in a rotated sorted array that may contain duplicates.
3 * Uses modified binary search to handle the rotation and duplicates.
4 *
5 * @param nums - The rotated sorted array with possible duplicates
6 * @param target - The value to search for
7 * @returns true if target exists in the array, false otherwise
8 */
9function search(nums: number[], target: number): boolean {
10 // Initialize left and right pointers for binary search
11 let left: number = 0;
12 let right: number = nums.length - 1;
13
14 while (left < right) {
15 // Calculate middle index using bit shift for efficiency
16 const middle: number = (left + right) >> 1;
17
18 // Case 1: Left half is sorted (middle element is greater than rightmost)
19 if (nums[middle] > nums[right]) {
20 // Check if target lies within the sorted left half
21 if (nums[left] <= target && target <= nums[middle]) {
22 // Target is in left half, move right pointer to middle
23 right = middle;
24 } else {
25 // Target is in right half, move left pointer past middle
26 left = middle + 1;
27 }
28 }
29 // Case 2: Right half is sorted (middle element is less than rightmost)
30 else if (nums[middle] < nums[right]) {
31 // Check if target lies within the sorted right half
32 if (nums[middle] < target && target <= nums[right]) {
33 // Target is in right half, move left pointer past middle
34 left = middle + 1;
35 } else {
36 // Target is in left half, move right pointer to middle
37 right = middle;
38 }
39 }
40 // Case 3: Cannot determine which half is sorted due to duplicates
41 else {
42 // Decrement right pointer to eliminate duplicate and continue
43 right--;
44 }
45 }
46
47 // Check if the remaining element is the target
48 return nums[left] === target;
49}
50
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the array. In the worst case, when the array contains many duplicate elements (especially when most elements equal to nums[r]
), the algorithm degenerates to linear search. This happens because when nums[mid] == nums[r]
, we can only decrement r
by 1 (r -= 1
), which means we might need to examine almost every element in the array. For example, if the array is [1, 1, 1, 1, 1, 1, 1, 0, 1]
and we're searching for 0
, we would need to decrement r
multiple times, resulting in O(n)
operations.
The space complexity is O(1)
, as the algorithm only uses a constant amount of extra space for variables l
, r
, mid
, and n
, regardless of the input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Handling of Boundary Comparisons
One of the most common mistakes is using incorrect comparison operators when checking if the target lies within the sorted portion of the array.
Pitfall Example:
# Incorrect - may miss the target when it equals nums[mid] if nums[left] < target < nums[mid]: # Wrong! Excludes boundaries right = mid
Why it's wrong: If target == nums[mid]
, this condition would be false, causing the algorithm to search in the wrong half of the array.
Solution:
# Correct - includes boundary values if nums[left] <= target <= nums[mid]: right = mid
2. Inefficient Handling of the Duplicate Case
When nums[mid] == nums[right]
, some implementations might try to skip multiple duplicates at once or make assumptions about the array structure.
Pitfall Example:
# Attempting to skip all duplicates at once - may miss the target while left < right and nums[mid] == nums[right]: right -= 1 mid = (left + right) // 2 # Recalculating mid inside the loop
Why it's wrong: This approach changes the mid value while processing duplicates, which can cause the algorithm to skip over the target or enter an infinite loop.
Solution:
# Correct - decrement right by only 1 and continue to next iteration else: right -= 1 # Let the main loop recalculate mid in the next iteration
3. Wrong Update of Search Boundaries
A subtle but critical mistake is using right = mid - 1
when we haven't confirmed that nums[mid]
is not the target.
Pitfall Example:
# Case 1: Left half is sorted if nums[mid] > nums[right]: if nums[left] <= target <= nums[mid]: right = mid - 1 # Wrong! Could skip the target if target == nums[mid]
Why it's wrong: Since we include nums[mid]
in the comparison (target <= nums[mid]
), the target could be at position mid
. Setting right = mid - 1
would exclude it from future searches.
Solution:
# Correct boundary updates if nums[mid] > nums[right]: if nums[left] <= target <= nums[mid]: right = mid # Keep mid in search range else: left = mid + 1 # Safe to exclude mid here
4. Forgetting the Final Check
After the binary search loop terminates, forgetting to check if the remaining element is the target.
Pitfall Example:
while left < right: # ... binary search logic ... return False # Wrong! Didn't check nums[left]
Solution:
while left < right: # ... binary search logic ... # Must check the final element return nums[left] == target
5. Not Handling Edge Cases
Failing to consider special cases like single-element arrays or when all elements are duplicates.
Pitfall Example:
# Not checking for empty array
def search(self, nums: List[int], target: int) -> bool:
left, right = 0, len(nums) - 1 # IndexError if nums is empty
Solution:
def search(self, nums: List[int], target: int) -> bool:
if not nums:
return False
n = len(nums)
left, right = 0, n - 1
# ... rest of the logic
These pitfalls highlight the importance of careful boundary handling and understanding the nuances of binary search in rotated arrays with duplicates. The key is to be conservative with boundary updates and ensure no potential target positions are accidentally excluded from the search space.
Which of the following problems can be solved with backtracking (select multiple)
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!