Skip to content

81. Search In Rotated Sorted Array Ii

class Solution:
    """🔄 Search in Rotated Sorted Array II.

    Problem:
    --------
    Given an integer array `nums`, sorted in non-decreasing order and then rotated at an
    unknown pivot, and an integer `target`, return whether `target` is in `nums`. Unlike
    LC 33, values may repeat. Reduce the number of operations as much as possible.

    Key Insight:
    ------------
    With distinct values (LC 33), at least one half around `mid` is always sorted, and
    `nums[low] <= nums[mid]` tells us which one. A sorted half answers "is the target in
    here?" in O(1), so each step discards half the range: O(log n).

    Duplicates break the test, not the structure. One half is still sorted, but when
    `nums[low] == nums[mid] == nums[high]` we cannot tell which:
    - `[1, 0, 1, 1, 1]`: the pivot is in the left half.
    - `[1, 1, 1, 0, 1]`: the pivot is in the right half.
    Trusting `nums[low] <= nums[mid]` here can discard the half holding the target.

    Approach:
    ---------
    Binary search as in LC 33, plus one case:
    - `nums[mid] == target`: found.
    - `nums[low] == nums[mid] == nums[high]`: ambiguous. `nums[low]` and `nums[high]`
      equal `nums[mid]`, which is not the target, so drop both ends and retry with a new
      `mid`. The `continue` matters: without it the stale `mid` is compared against the
      shrunk range, and a one-element window steps `low` past the end of the array.
    - Otherwise one half is identifiably sorted: keep it if `target` lies in its range,
      else keep the other half.

    Complexity:
    -----------
    - Time: O(log n) when values are distinct, O(n) worst case. The O(n) is unavoidable:
      in `[1, 1, 1, 1, 0, 1, 1]` every probe of a `1` says nothing about where the `0` is,
      so any algorithm may have to inspect every position.
    - Space: O(1)
    """

    def search(self, nums: list[int], target: int) -> bool:
        low, high = 0, len(nums) - 1

        while low <= high:
            mid = (low + high) // 2
            if nums[mid] == target:
                return True

            # Both ends equal nums[mid] (not the target): drop them, recompute mid
            if nums[low] == nums[mid] == nums[high]:
                low, high = low + 1, high - 1
                continue

            if nums[low] <= nums[mid]:
                # [low, mid] is sorted
                if nums[low] <= target < nums[mid]:
                    high = mid - 1
                else:
                    low = mid + 1
            else:
                # [mid, high] is sorted
                if nums[mid] < target <= nums[high]:
                    low = mid + 1
                else:
                    high = mid - 1

        return False