Facebook Pixel

33. Search in Rotated Sorted Array

Problem Description

You are given an integer array nums that was originally sorted in ascending order with all distinct values. However, this array may have been left rotated at some unknown pivot index k (where 1 <= k < nums.length).

A left rotation at index k means the array is rearranged from its original order [nums[0], nums[1], ..., nums[n-1]] to [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]].

For example:

  • Original sorted array: [0,1,2,4,5,6,7]
  • After left rotation by 3 indices: [4,5,6,7,0,1,2]

Your task is to search for a given target value in this possibly rotated array and return its index. If the target is not found in the array, return -1.

Key Requirements:

  • The algorithm must have O(log n) runtime complexity
  • The array contains distinct values (no duplicates)
  • The array may or may not be rotated

The challenge is to efficiently search in this rotated sorted array without first finding the rotation point or restoring the original order. Since the required time complexity is O(log n), a linear search is not acceptable - you need to use a modified binary search approach that can handle the rotation.

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

Intuition

When we look at a rotated sorted array, we notice an important property: if we split the array at any point, at least one half will always be properly sorted. This is the key insight that allows us to use binary search even though the entire array isn't sorted.

Let's understand why this works. Consider the array [4,5,6,7,0,1,2]:

  • If we pick the middle element (index 3, value 7), we can check:
    • Left half: [4,5,6,7] - this is sorted (4 ≤ 5 ≤ 6 ≤ 7)
    • Right half: [7,0,1,2] - this is not sorted (7 > 0)

The magic happens because we can easily identify which half is sorted by comparing the first and last elements of that half. If nums[left] <= nums[mid], then the left half is sorted. Otherwise, the right half must be sorted.

Once we know which half is sorted, we can make an intelligent decision about where to search next:

  • If the sorted half contains our target (we can check this easily since it's sorted), we search there
  • Otherwise, the target must be in the unsorted half (if it exists at all)

We can frame this as a boundary-finding problem: we're looking for the first index where "we've found the target or passed where it should be." The feasible function considers both the rotation and the target location.

This approach works because:

  1. We can always identify at least one sorted portion
  2. In a sorted portion, we can definitively determine if the target lies within its range by checking the boundaries
  3. This allows us to eliminate half of the search space in each iteration, maintaining O(log n) complexity

The beauty of this solution is that we don't need to find the rotation point first. We simply adapt our binary search to handle both the sorted and rotated portions dynamically as we encounter them.

Learn more about Binary Search patterns.

Solution Implementation

1class Solution:
2    def search(self, nums: List[int], target: int) -> int:
3        """
4        Search for a target value in a rotated sorted array using the binary search template.
5
6        Args:
7            nums: A rotated sorted array with unique elements
8            target: The value to search for
9
10        Returns:
11            The index of target if found, -1 otherwise
12        """
13        n = len(nums)
14        left, right = 0, n - 1
15        first_true_index = -1
16
17        def feasible(mid: int) -> bool:
18            """
19            Returns True if target is at mid or should be to its left.
20            """
21            if nums[0] <= nums[mid]:
22                # Left half is sorted
23                return nums[0] <= target <= nums[mid]
24            else:
25                # Right half is sorted
26                # Feasible if target is NOT in the sorted right half
27                return not (nums[mid] < target <= nums[n - 1])
28
29        while left <= right:
30            mid = (left + right) // 2
31            if feasible(mid):
32                first_true_index = mid
33                right = mid - 1
34            else:
35                left = mid + 1
36
37        # Check if the element at first_true_index is the target
38        if first_true_index == -1:
39            return -1
40        return first_true_index if nums[first_true_index] == target else -1
41
1class Solution {
2    /**
3     * Search for a target value in a rotated sorted array using the binary search template.
4     *
5     * @param nums   the rotated sorted array
6     * @param target the target value to search for
7     * @return       the index of target if found, otherwise -1
8     */
9    public int search(int[] nums, int target) {
10        int n = nums.length;
11        int left = 0;
12        int right = n - 1;
13        int firstTrueIndex = -1;
14
15        while (left <= right) {
16            int mid = left + (right - left) / 2;
17
18            // Determine if feasible: target is at mid or should be to its left
19            boolean feasible;
20            if (nums[0] <= nums[mid]) {
21                // Left half is sorted
22                feasible = nums[0] <= target && target <= nums[mid];
23            } else {
24                // Right half is sorted
25                // Feasible if target is NOT in the sorted right half
26                feasible = !(nums[mid] < target && target <= nums[n - 1]);
27            }
28
29            if (feasible) {
30                firstTrueIndex = mid;
31                right = mid - 1;
32            } else {
33                left = mid + 1;
34            }
35        }
36
37        // Check if the element at firstTrueIndex is the target
38        if (firstTrueIndex == -1) {
39            return -1;
40        }
41        return nums[firstTrueIndex] == target ? firstTrueIndex : -1;
42    }
43}
44
1class Solution {
2public:
3    int search(vector<int>& nums, int target) {
4        int n = nums.size();
5        int left = 0;
6        int right = n - 1;
7        int firstTrueIndex = -1;
8
9        while (left <= right) {
10            int mid = left + (right - left) / 2;
11
12            // Determine if feasible: target is at mid or should be to its left
13            bool feasible;
14            if (nums[0] <= nums[mid]) {
15                // Left half is sorted
16                feasible = nums[0] <= target && target <= nums[mid];
17            } else {
18                // Right half is sorted
19                // Feasible if target is NOT in the sorted right half
20                feasible = !(nums[mid] < target && target <= nums[n - 1]);
21            }
22
23            if (feasible) {
24                firstTrueIndex = mid;
25                right = mid - 1;
26            } else {
27                left = mid + 1;
28            }
29        }
30
31        // Check if the element at firstTrueIndex is the target
32        if (firstTrueIndex == -1) {
33            return -1;
34        }
35        return nums[firstTrueIndex] == target ? firstTrueIndex : -1;
36    }
37};
38
1/**
2 * Searches for a target value in a rotated sorted array using the binary search template.
3 *
4 * @param nums - The rotated sorted array to search in
5 * @param target - The target value to find
6 * @returns The index of the target if found, otherwise -1
7 */
8function search(nums: number[], target: number): number {
9    const n: number = nums.length;
10    let left: number = 0;
11    let right: number = n - 1;
12    let firstTrueIndex: number = -1;
13
14    while (left <= right) {
15        const mid: number = Math.floor((left + right) / 2);
16
17        // Determine if feasible: target is at mid or should be to its left
18        let feasible: boolean;
19        if (nums[0] <= nums[mid]) {
20            // Left half is sorted
21            feasible = nums[0] <= target && target <= nums[mid];
22        } else {
23            // Right half is sorted
24            // Feasible if target is NOT in the sorted right half
25            feasible = !(nums[mid] < target && target <= nums[n - 1]);
26        }
27
28        if (feasible) {
29            firstTrueIndex = mid;
30            right = mid - 1;
31        } else {
32            left = mid + 1;
33        }
34    }
35
36    // Check if the element at firstTrueIndex is the target
37    if (firstTrueIndex === -1) {
38        return -1;
39    }
40    return nums[firstTrueIndex] === target ? firstTrueIndex : -1;
41}
42

Solution Approach

We use the standard binary search template with first_true_index to track our answer. The key insight is defining a feasible function that tells us whether the target could be at or before the current position.

Template Structure:

left, right = 0, n - 1
first_true_index = -1

while left <= right:
    mid = (left + right) // 2
    if feasible(mid):
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1

return first_true_index if first_true_index != -1 and nums[first_true_index] == target else -1

Defining the Feasible Function:

The feasible function returns True when the target is at index mid or should be to its left. This depends on which half is sorted:

  1. If left half is sorted (nums[0] <= nums[mid]):

    • Feasible if nums[0] <= target <= nums[mid] (target is in sorted left half)
  2. If right half is sorted (nums[0] > nums[mid]):

    • Feasible if target is NOT in the sorted right half
    • That is, NOT (nums[mid] < target <= nums[n-1])

Why This Works:

When feasible returns True, we save the current mid as a potential answer and search left for an earlier occurrence. When feasible returns False, the target must be to the right.

After the loop, first_true_index holds the position where our feasible condition first became true. We then verify that nums[first_true_index] == target to confirm the target exists.

Time Complexity: O(log n) - we eliminate half of the search space in each iteration Space Complexity: O(1) - only using a constant amount of extra space for pointers

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 array [4,5,6,7,0,1,2] searching for target = 0.

Initial State:

  • Array: [4,5,6,7,0,1,2] (indices 0-6)
  • left = 0, right = 6, first_true_index = -1, target = 0

Iteration 1:

  • Calculate mid = (0 + 6) // 2 = 3
  • nums[mid] = 7
  • Check if left half is sorted: nums[0] = 4 <= nums[3] = 7
  • Is target in sorted left half? Check if 4 <= 0 <= 7: No (0 < 4)
  • Feasible = False → left = mid + 1 = 4

Iteration 2:

  • left = 4, right = 6
  • Calculate mid = (4 + 6) // 2 = 5
  • nums[mid] = 1
  • Check if left half is sorted: nums[0] = 4 <= nums[5] = 1? No
  • Right half is sorted. Is target in sorted right half? 1 < 0 <= 2? No (0 < 1)
  • Feasible = True → first_true_index = 5, right = mid - 1 = 4

Iteration 3:

  • left = 4, right = 4
  • Calculate mid = (4 + 4) // 2 = 4
  • nums[mid] = 0
  • Check if left half is sorted: nums[0] = 4 <= nums[4] = 0? No
  • Right half is sorted. Is target in sorted right half? 0 < 0 <= 2? No (0 is not > 0)
  • Feasible = True → first_true_index = 4, right = mid - 1 = 3

Iteration 4:

  • left = 4, right = 3
  • Loop condition left <= right is false, exit loop

Final Check:

  • first_true_index = 4
  • Does nums[4] == target? Yes, 0 == 0
  • Return 4

The algorithm successfully finds the target at index 4, demonstrating the O(log n) efficiency with the standard binary search template.

Time and Space Complexity

Time Complexity: O(log n), where n is the length of the array nums. The algorithm uses the binary search template with while left <= right. In each iteration, the search space is reduced by half (either updating left = mid + 1 or right = mid - 1), which results in at most log₂(n) iterations.

Space Complexity: O(1). The algorithm only uses a constant amount of extra space for the variables n, left, right, mid, and first_true_index, regardless of the input size. No additional data structures or recursive calls are used.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Using Wrong Binary Search Template Variant

The Pitfall: Using while left < right with right = mid instead of the standard template with first_true_index tracking. This variant can be confusing and error-prone.

Example of the Issue:

# Non-standard variant (harder to reason about)
while left < right:
    mid = (left + right) >> 1
    if condition:
        right = mid
    else:
        left = mid + 1
return left if nums[left] == target else -1

Solution: Use the standard binary search template with explicit answer tracking:

left, right = 0, n - 1
first_true_index = -1

while left <= right:
    mid = (left + right) // 2
    if feasible(mid):
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1

return first_true_index if first_true_index != -1 and nums[first_true_index] == target else -1

2. Incorrect Boundary Comparison When Determining the Sorted Half

The Pitfall: Using strict inequality (nums[0] < nums[mid]) instead of nums[0] <= nums[mid] when determining which half is sorted.

Example of the Issue:

if nums[0] < nums[mid]:  # Wrong! Missing the equal case
    # Left half logic

Consider the array [3, 1] searching for target 1:

  • With strict inequality: 3 < 3 is false, incorrectly assuming right half is sorted
  • With correct comparison: 3 <= 3 is true, correctly identifying left half pattern

Solution: Always use <= to properly handle all cases:

if nums[0] <= nums[mid]:  # Correct
    # Left half is sorted

3. Off-by-One Errors in Feasible Function

The Pitfall: When checking if the target falls within a sorted range, using incorrect comparison operators.

Example of the Issue:

# Wrong comparison for right half
feasible = not (nums[mid] <= target <= nums[n - 1])  # Includes mid incorrectly

Solution: Use the correct comparisons:

# For left sorted half (target at or before mid):
feasible = nums[0] <= target <= nums[mid]

# For right sorted half (target NOT in right half):
feasible = not (nums[mid] < target <= nums[n - 1])  # Note: strictly < for mid

4. Forgetting to Validate first_true_index

The Pitfall: Not checking if first_true_index is still -1 (no valid position found) or if the element at that position equals the target.

Example of the Issue:

# Missing validation
return first_true_index  # Wrong! Might return -1 or wrong index

Solution: Always validate after the loop:

if first_true_index == -1:
    return -1
return first_true_index if nums[first_true_index] == target else -1
Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

The three-steps of Depth First Search are:

  1. Identify states;
  2. Draw the state-space tree;
  3. DFS on the state-space tree.

Recommended Readings

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

Load More