Facebook Pixel

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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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]:

  1. 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] and mid points to 7, we know [4,5,6,7] is sorted because 7 > 2.

  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] and mid points to 1, we know [1,2,3,4] is sorted because 1 < 4.

  3. 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], when mid and r 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 boundary r = n - 1, where n 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

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

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 of r = mid - 1 because mid could be the target
  • In Case 2, we use l = mid + 1 because we've already confirmed nums[mid] != target (since nums[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 Evaluator

Example 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 (since 1 is not less than 1)
  • 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following problems can be solved with backtracking (select multiple)


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More