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]andmidpoints 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]andmidpoints 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], whenmidandrboth 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 = 0and right boundaryr = n - 1, wherenis the length of the array - We'll search while 
l < rto 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 
rby 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 = midinstead ofr = mid - 1becausemidcould be the target - In Case 2, we use 
l = mid + 1because 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 = 4nums[mid] = 7,nums[r] = 4- Since 
7 > 4(Case 1), the left portion[4,5,6,6,7]is sorted - Check if target 
1is 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 = 9mid = (5 + 9) >> 1 = 7nums[mid] = 2,nums[r] = 4- Since 
2 < 4(Case 2), the right portion[2,4,4]is sorted - Check if target 
1is 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 = 7mid = (5 + 7) >> 1 = 6nums[mid] = 1,nums[r] = 2- Since 
1 < 2(Case 2), the right portion[1,2]is sorted - Check if target 
1is in sorted right:1 < 1 <= 2? No (since1is 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 = 6mid = (5 + 6) >> 1 = 5nums[mid] = 0,nums[r] = 1- Since 
0 < 1(Case 2), the right portion is sorted - Check if target 
1is 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 = 6nums[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
491class 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}
461class 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};
391/**
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}
50Time 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.
Consider the classic dynamic programming of longest increasing subsequence:
Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.
For example, the length of LIS for [50, 3, 10, 7, 40, 80] is 4 and LIS is
[3, 7, 40, 80].
What is the recurrence relation?
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 If you prefer videos here's a video that explains recursion in a fun and easy way 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 him
Want a Structured Path to Master System Design Too? Don’t Miss This!