title: Search in Rotated Sorted Array
slug: search-in-rotated-sorted-array
difficulty: medium
leetcode_id: 33
leetcode_url: https://leetcode.com/problems/search-in-rotated-sorted-array/
categories:
  - arrays
  - binary-search
patterns:
  - binary-search

function_signature: "def search(nums: list[int], target: int) -> int:"

test_cases:
  visible:
    - input: { nums: [4, 5, 6, 7, 0, 1, 2], target: 0 }
      expected: 4
    - input: { nums: [4, 5, 6, 7, 0, 1, 2], target: 3 }
      expected: -1
    - input: { nums: [1], target: 0 }
      expected: -1
  hidden:
    - input: { nums: [1], target: 1 }
      expected: 0
    - input: { nums: [3, 1], target: 1 }
      expected: 1
    - input: { nums: [5, 1, 3], target: 5 }
      expected: 0
    - input: { nums: [1, 2, 3, 4, 5], target: 3 }
      expected: 2

description: |
  There is an integer array `nums` sorted in ascending order (with **distinct** values).

  Prior to being passed to your function, `nums` is **possibly rotated** at an unknown pivot index `k` (`1 <= k < nums.length`) such that the resulting array is `[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]` (0-indexed). For example, `[0,1,2,4,5,6,7]` might be rotated at pivot index `3` and become `[4,5,6,7,0,1,2]`.

  Given the array `nums` **after** the possible rotation and an integer `target`, return *the index of* `target` *if it is in* `nums`*, or* `-1` *if it is not in* `nums`.

  You must write an algorithm with **O(log n)** runtime complexity.

constraints: |
  - `1 <= nums.length <= 5000`
  - `-10^4 <= nums[i] <= 10^4`
  - All values of `nums` are **unique**
  - `nums` is an ascending array that is possibly rotated
  - `-10^4 <= target <= 10^4`

examples:
  - input: "nums = [4,5,6,7,0,1,2], target = 0"
    output: "4"
    explanation: "The target 0 is found at index 4."
  - input: "nums = [4,5,6,7,0,1,2], target = 3"
    output: "-1"
    explanation: "The target 3 is not in the array, so return -1."
  - input: "nums = [1], target = 0"
    output: "-1"
    explanation: "Single element array doesn't contain the target."

explanation:
  intuition: |
    Imagine a sorted array as a staircase going upward. When you rotate it, you're cutting the staircase at some point and moving the upper portion to the end — creating a "broken" staircase with two ascending sections.

    For example, `[0,1,2,4,5,6,7]` becomes `[4,5,6,7,0,1,2]`. We now have two sorted "halves": `[4,5,6,7]` (the higher half) and `[0,1,2]` (the lower half).

    The key insight is: **at least one half of the array is always properly sorted**. When you pick a middle element, you can determine which half is sorted by comparing the endpoints. Then you check if your target falls within that sorted range — if yes, search there; if not, search the other half.

    Think of it like this: you're at the middle of a broken staircase. You look left and right. One side will be a clean, unbroken staircase (sorted). You can quickly tell if your target is on that sorted side. If not, it must be on the "broken" side, so you continue searching there.

  approach: |
    We solve this using **Modified Binary Search**:

    **Step 1: Initialise pointers**

    - `left = 0`, `right = len(nums) - 1`
    - We'll search within `[left, right]` inclusive

    &nbsp;

    **Step 2: Binary search with sorted-half detection**

    - While `left <= right`:
      - Calculate `mid = left + (right - left) // 2`
      - If `nums[mid] == target`: return `mid`
      - **Determine which half is sorted**:
        - If `nums[left] <= nums[mid]`: **left half is sorted**
          - If `nums[left] <= target < nums[mid]`: target is in left half, set `right = mid - 1`
          - Else: target is in right half, set `left = mid + 1`
        - Else: **right half is sorted**
          - If `nums[mid] < target <= nums[right]`: target is in right half, set `left = mid + 1`
          - Else: target is in left half, set `right = mid - 1`

    &nbsp;

    **Step 3: Return -1 if not found**

    - If the loop exits without finding the target, return `-1`

    &nbsp;

    The algorithm works because we can always identify one sorted half and determine if the target lies within that range. If it does, we search there; otherwise, we search the other (potentially rotated) half.

  common_pitfalls:
    - title: Confusing Which Half is Sorted
      description: |
        The condition `nums[left] <= nums[mid]` (with `<=`, not `<`) correctly identifies when the left half is sorted. The `=` is important for cases where `left == mid` (small subarrays).

        For example, with `[3, 1]` and `left = 0, mid = 0`:
        - `nums[left] == nums[mid] == 3`
        - The left "half" (just element 3) is trivially sorted

        Using `<` instead of `<=` would incorrectly identify the right half as sorted.
      wrong_approach: "if nums[left] < nums[mid] (missing equality)"
      correct_approach: "if nums[left] <= nums[mid]"

    - title: Incorrect Target Range Checks
      description: |
        When checking if the target is in the sorted half, be precise about the boundaries:
        - For left half: `nums[left] <= target < nums[mid]` (exclude mid since we already checked it)
        - For right half: `nums[mid] < target <= nums[right]` (exclude mid)

        Missing the equality check with the endpoints can cause you to search the wrong half.
      wrong_approach: "nums[left] < target < nums[mid]"
      correct_approach: "nums[left] <= target < nums[mid]"

    - title: Using Two-Pass Approach
      description: |
        A tempting approach is to first find the pivot (minimum element) using "Find Minimum in Rotated Sorted Array", then do a standard binary search on the correct half. While this works, it requires **two passes** (O(log n) + O(log n)).

        The single-pass approach is cleaner and more elegant — you determine the sorted half and search direction simultaneously in one loop.
      wrong_approach: "Find pivot first, then binary search"
      correct_approach: "Single binary search with sorted-half detection"

  key_takeaways:
    - "**One sorted half**: In a rotated sorted array, at least one half is always properly sorted — use this property to guide your search"
    - "**Compare with endpoints**: Comparing `nums[left]` with `nums[mid]` tells you which half is sorted"
    - "**Range membership**: Once you identify the sorted half, checking if target is in that range is straightforward"
    - "**Foundation problem**: This technique is the basis for many rotated array problems (find minimum, search with duplicates, etc.)"

  time_complexity: "O(log n). Each iteration eliminates half the search space."
  space_complexity: "O(1). Only a constant number of pointer variables are used."

solutions:
  - approach_name: Binary Search with Sorted-Half Detection
    is_optimal: true
    code: |
      def search(nums: list[int], target: int) -> int:
          left, right = 0, len(nums) - 1

          while left <= right:
              mid = left + (right - left) // 2

              # Found the target
              if nums[mid] == target:
                  return mid

              # Determine which half is sorted
              if nums[left] <= nums[mid]:
                  # Left half is sorted
                  if nums[left] <= target < nums[mid]:
                      # Target is in the sorted left half
                      right = mid - 1
                  else:
                      # Target is in the right half
                      left = mid + 1
              else:
                  # Right half is sorted
                  if nums[mid] < target <= nums[right]:
                      # Target is in the sorted right half
                      left = mid + 1
                  else:
                      # Target is in the left half
                      right = mid - 1

          # Target not found
          return -1
    explanation: |
      **Time Complexity:** O(log n) — Search space halves each iteration.

      **Space Complexity:** O(1) — Constant extra space.

      At each step, we identify which half is sorted (by comparing `nums[left]` with `nums[mid]`), then check if the target falls within that sorted range. If yes, we search that half; otherwise, we search the other half. This ensures we always make progress toward the target or confirm its absence.

  - approach_name: Two-Pass (Find Pivot + Binary Search)
    is_optimal: false
    code: |
      def search(nums: list[int], target: int) -> int:
          n = len(nums)

          # Step 1: Find the pivot (index of minimum element)
          left, right = 0, n - 1
          while left < right:
              mid = left + (right - left) // 2
              if nums[mid] > nums[right]:
                  left = mid + 1
              else:
                  right = mid
          pivot = left

          # Step 2: Determine which half to search
          # If target >= nums[0], it's in the left portion (before pivot)
          # Otherwise, it's in the right portion (pivot to end)
          if target >= nums[0]:
              left, right = 0, pivot - 1 if pivot > 0 else 0
          else:
              left, right = pivot, n - 1

          # Handle edge case: array not rotated or single element
          if pivot == 0:
              left, right = 0, n - 1

          # Step 3: Standard binary search in the chosen range
          while left <= right:
              mid = left + (right - left) // 2
              if nums[mid] == target:
                  return mid
              elif nums[mid] < target:
                  left = mid + 1
              else:
                  right = mid - 1

          return -1
    explanation: |
      **Time Complexity:** O(log n) — Two binary searches, each O(log n).

      **Space Complexity:** O(1) — Constant extra space.

      This approach first finds the pivot (minimum element) to understand the array's structure, then performs a standard binary search on the appropriate half. While correct and having the same complexity, it's more verbose than the single-pass approach. Useful if you've already solved "Find Minimum in Rotated Sorted Array" and want to build on that.

  - approach_name: Linear Search
    is_optimal: false
    code: |
      def search(nums: list[int], target: int) -> int:
          # Simple scan through the array
          for i, num in enumerate(nums):
              if num == target:
                  return i
          return -1
    explanation: |
      **Time Complexity:** O(n) — Scans every element.

      **Space Complexity:** O(1) — No extra space used.

      A straightforward linear scan. This doesn't meet the O(log n) requirement but demonstrates the baseline approach. With constraints up to `n = 5000`, linear search would still pass but isn't optimal.
