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.
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)
- Left half:
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)
This approach works because:
- We can always identify at least one sorted portion
- In a sorted portion, we can definitively determine if the target lies within its range by checking the boundaries
- 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 Approach
We implement a modified binary search that handles the rotation by identifying which half of the current search range is sorted.
Algorithm Steps:
-
Initialize pointers: Set
left = 0
andright = n - 1
to define our search boundaries. -
Binary search loop: While
left < right
:- Calculate the middle index:
mid = (left + right) >> 1
(using bit shift for division by 2) - Determine which half is sorted by checking if
nums[0] <= nums[mid]
- Calculate the middle index:
-
Case 1: Left half is sorted (
nums[0] <= nums[mid]
):- The range
[0...mid]
forms a sorted array - Check if target is within this sorted range:
nums[0] <= target <= nums[mid]
- If yes, narrow search to left half:
right = mid
- If no, target must be in right half:
left = mid + 1
- The range
-
Case 2: Right half is sorted (when left half is not sorted):
- The range
[mid+1...n-1]
must be sorted - Check if target is within this sorted range:
nums[mid] < target <= nums[n-1]
- If yes, narrow search to right half:
left = mid + 1
- If no, target must be in left half:
right = mid
- The range
-
Final check: After the loop terminates (when
left >= right
):- Check if
nums[left] == target
- Return
left
if match found, otherwise return-1
- Check if
Key Implementation Details:
- We use
right = mid
instead ofright = mid - 1
in some cases because our target might be atmid
position - The condition
nums[0] <= nums[mid]
effectively tells us if the rotation point is in the right half - We compare with
nums[0]
andnums[n-1]
to determine the boundaries of sorted portions - The bit shift operation
>> 1
is used for efficient division by 2
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 EvaluatorExample 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
,target = 0
Iteration 1:
- Calculate
mid = (0 + 6) >> 1 = 3
nums[mid] = 7
- Check if left half is sorted:
nums[0] = 4 <= nums[3] = 7
✓ (left half[4,5,6,7]
is sorted) - Is target in sorted left half? Check if
4 <= 0 <= 7
- No, because
0 < 4
- No, because
- Target must be in right half, so set
left = mid + 1 = 4
Iteration 2:
- Current search range: indices 4-6 →
[0,1,2]
left = 4
,right = 6
- Calculate
mid = (4 + 6) >> 1 = 5
nums[mid] = 1
- Check if left half is sorted:
nums[0] = 4 <= nums[5] = 1
? No (comparing with originalnums[0]
) - Since left half is not sorted, right half must be sorted
- Check if target is in sorted right half:
nums[5] = 1 < 0 <= nums[6] = 2
?- No, because
0 < 1
- No, because
- Target must be in left half, so set
right = mid = 5
Iteration 3:
- Current search range: indices 4-5 →
[0,1]
left = 4
,right = 5
- Calculate
mid = (4 + 5) >> 1 = 4
nums[mid] = 0
- Check if left half is sorted:
nums[0] = 4 <= nums[4] = 0
? No - Since left half is not sorted, right half must be sorted
- Check if target is in sorted right half:
nums[4] = 0 < 0 <= nums[6] = 2
?- No, because
0 = 0
(not strictly greater)
- No, because
- Target must be in left half, so set
right = mid = 4
Iteration 4:
left = 4
,right = 4
- Loop condition
left < right
is false, exit loop
Final Check:
nums[left] = nums[4] = 0
- Does
nums[4] == target
? Yes,0 == 0
✓ - Return
4
The algorithm successfully finds the target at index 4 in just 3 iterations, demonstrating the O(log n)
efficiency.
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.
5 Returns the index of the target if found, otherwise returns -1.
6
7 Args:
8 nums: A rotated sorted array with unique elements
9 target: The value to search for
10
11 Returns:
12 The index of target if found, -1 otherwise
13 """
14 n = len(nums)
15 left, right = 0, n - 1
16
17 # Binary search to find the target
18 while left < right:
19 # Calculate middle index using bit shift (equivalent to // 2)
20 mid = (left + right) >> 1
21
22 # Check if the left half is sorted (pivot is in right half)
23 if nums[0] <= nums[mid]:
24 # Target is in the sorted left half
25 if nums[0] <= target <= nums[mid]:
26 right = mid # Search in left half including mid
27 else:
28 left = mid + 1 # Search in right half excluding mid
29 else:
30 # Left half contains the pivot, right half is sorted
31 # Target is in the sorted right half
32 if nums[mid] < target <= nums[n - 1]:
33 left = mid + 1 # Search in right half excluding mid
34 else:
35 right = mid # Search in left half including mid
36
37 # Check if the element at final position is the target
38 return left if nums[left] == target else -1
39
1class Solution {
2 /**
3 * Search for a target value in a rotated sorted array.
4 * The array was originally sorted in ascending order, then rotated at some pivot.
5 *
6 * @param nums the rotated sorted array
7 * @param target the target value to search for
8 * @return the index of target if found, otherwise -1
9 */
10 public int search(int[] nums, int target) {
11 int arrayLength = nums.length;
12 int left = 0;
13 int right = arrayLength - 1;
14
15 // Binary search with rotation handling
16 while (left < right) {
17 int mid = (left + right) >> 1; // Equivalent to (left + right) / 2
18
19 // Check if the left half is sorted
20 if (nums[0] <= nums[mid]) {
21 // Target is in the sorted left half
22 if (nums[0] <= target && target <= nums[mid]) {
23 right = mid; // Search in left half
24 } else {
25 left = mid + 1; // Search in right half
26 }
27 } else {
28 // The right half must be sorted
29 // Target is in the sorted right half
30 if (nums[mid] < target && target <= nums[arrayLength - 1]) {
31 left = mid + 1; // Search in right half
32 } else {
33 right = mid; // Search in left half
34 }
35 }
36 }
37
38 // Check if the element at the final position is the target
39 return nums[left] == target ? left : -1;
40 }
41}
42
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
8 // Binary search in rotated sorted array
9 while (left < right) {
10 // Calculate middle index using bit shift (equivalent to division by 2)
11 int mid = (left + right) >> 1;
12
13 // Check if the left half is sorted
14 if (nums[0] <= nums[mid]) {
15 // Left half is sorted
16 // Check if target lies in the sorted left half
17 if (nums[0] <= target && target <= nums[mid]) {
18 // Target is in left half, search left
19 right = mid;
20 } else {
21 // Target is in right half, search right
22 left = mid + 1;
23 }
24 } else {
25 // Right half is sorted
26 // Check if target lies in the sorted right half
27 if (nums[mid] < target && target <= nums[n - 1]) {
28 // Target is in right half, search right
29 left = mid + 1;
30 } else {
31 // Target is in left half, search left
32 right = mid;
33 }
34 }
35 }
36
37 // Check if the element at final position is the target
38 return nums[left] == target ? left : -1;
39 }
40};
41
1/**
2 * Searches for a target value in a rotated sorted array using binary search.
3 * The array was originally sorted in ascending order, then rotated at some pivot.
4 *
5 * @param nums - The rotated sorted array to search in
6 * @param target - The target value to find
7 * @returns The index of the target if found, otherwise -1
8 */
9function search(nums: number[], target: number): number {
10 const arrayLength: number = nums.length;
11 let leftPointer: number = 0;
12 let rightPointer: number = arrayLength - 1;
13
14 // Binary search with rotation handling
15 while (leftPointer < rightPointer) {
16 // Calculate middle index using bit shift for efficiency
17 const middleIndex: number = (leftPointer + rightPointer) >> 1;
18
19 // Check if the left half is sorted (no rotation point in left half)
20 if (nums[0] <= nums[middleIndex]) {
21 // Target is within the sorted left half
22 if (nums[0] <= target && target <= nums[middleIndex]) {
23 // Search in the left half
24 rightPointer = middleIndex;
25 } else {
26 // Target must be in the right half
27 leftPointer = middleIndex + 1;
28 }
29 } else {
30 // The rotation point is in the left half, so right half is sorted
31 // Target is within the sorted right half
32 if (nums[middleIndex] < target && target <= nums[arrayLength - 1]) {
33 // Search in the right half
34 leftPointer = middleIndex + 1;
35 } else {
36 // Target must be in the left half
37 rightPointer = middleIndex;
38 }
39 }
40 }
41
42 // Check if the element at the final position is the target
43 return nums[leftPointer] === target ? leftPointer : -1;
44}
45
Time and Space Complexity
Time Complexity: O(log n)
, where n
is the length of the array nums
. The algorithm uses binary search to find the target element in a rotated sorted array. In each iteration of the while loop, the search space is reduced by half (either updating left = mid + 1
or right = mid
), 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
, and mid
, regardless of the input size. No additional data structures or recursive calls are used that would scale with the input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Boundary Comparison When Determining the Sorted Half
The Pitfall:
A common mistake is using strict inequality (nums[0] < nums[mid]
) instead of nums[0] <= nums[mid]
when determining which half is sorted. This causes incorrect behavior when nums[0] == nums[mid]
, which can happen in small arrays or specific rotation scenarios.
Example of the Issue:
# Incorrect implementation 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 or entire array from 0 to mid is sorted
2. Off-by-One Errors in Range Checking
The Pitfall: When checking if the target falls within a sorted range, developers often make boundary errors by excluding valid endpoints or using incorrect comparison operators.
Example of the Issue:
# Incorrect: Missing boundary values if nums[0] < target < nums[mid]: # Wrong! Excludes valid endpoints right = mid # Another mistake: Incorrect boundary for right half if nums[mid] <= target <= nums[n - 1]: # Wrong! Would include mid twice left = mid + 1
Solution: Use inclusive comparisons for the sorted range boundaries:
# For left sorted half: if nums[0] <= target <= nums[mid]: right = mid # Include mid since target could equal nums[mid] # For right sorted half: if nums[mid] < target <= nums[n - 1]: # Exclude mid since we already checked it left = mid + 1
3. Inconsistent Mid-Point Updates
The Pitfall:
Mixing right = mid - 1
and right = mid
patterns inconsistently, or using left = mid
without adding 1, leading to infinite loops.
Example of the Issue:
# This can cause infinite loop while left < right: mid = (left + right) >> 1 # ... some conditions ... left = mid # Dangerous! Can cause infinite loop when left = right - 1
Solution: Be consistent with the update pattern:
- When moving left pointer: always use
left = mid + 1
- When moving right pointer: use
right = mid
(since we might need to include mid in search) - Ensure the loop condition matches the update pattern (
while left < right
with this approach)
4. Forgetting to Handle Edge Cases
The Pitfall: Not properly handling arrays with only 1 or 2 elements, or not checking if the final position contains the target.
Example of the Issue:
# Forgetting final check while left < right: # ... binary search logic ... return -1 # Wrong! Didn't check if nums[left] == target
Solution: Always validate the final position after the loop:
return left if nums[left] == target else -1
Also ensure the algorithm works for minimal cases:
- Single element:
[1]
searching for1
or0
- Two elements:
[2, 1]
with various targets - Non-rotated array:
[1, 2, 3, 4, 5]
Which algorithm should you use to find a node that is close to the root of the tree?
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 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
Want a Structured Path to Master System Design Too? Don’t Miss This!