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 use a direct binary search approach. At each step, we check if nums[mid] is the target. If not, we determine which half is sorted and decide where to search next.
Algorithm Steps:
-
Check for match: If
nums[mid] == target, returnmidimmediately. -
Determine which half is sorted: Compare
nums[left]withnums[mid].- If
nums[left] <= nums[mid], the left half[left, mid]is sorted. - Otherwise, the right half
[mid, right]is sorted.
- If
-
Decide where to search:
- If the left half is sorted and
nums[left] <= target < nums[mid], search left. - If the right half is sorted and
nums[mid] < target <= nums[right], search right. - Otherwise, search the other half.
- If the left half is sorted and
Why This Works:
In a rotated sorted array, at least one half is always properly sorted. For the sorted half, we can easily check if the target lies within its range by comparing boundary values. If the target is in the sorted range, we search there. Otherwise, the target must be in the unsorted half (if it 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 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) // 2 = 3 nums[mid] = 7, not equal to target 0- 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) - Target not in left half → search right:
left = mid + 1 = 4
Iteration 2:
left = 4,right = 6- Calculate
mid = (4 + 6) // 2 = 5 nums[mid] = 1, not equal to target 0- Check if left half is sorted:
nums[4] = 0 <= nums[5] = 1✓ - Is target in sorted left half? Check if
0 <= 0 < 1: Yes! - Target in left half → search left:
right = mid - 1 = 4
Iteration 3:
left = 4,right = 4- Calculate
mid = (4 + 4) // 2 = 4 nums[mid] = 0, equal to target 0 ✓- Return
4
The algorithm successfully finds the target at index 4 in just 3 iterations, demonstrating O(log n) efficiency.
Solution Implementation
1class Solution:
2 def search(self, nums: List[int], target: int) -> int:
3 left, right = 0, len(nums) - 1
4
5 while left <= right:
6 mid = (left + right) // 2
7
8 if nums[mid] == target:
9 return mid
10
11 # Check which half is sorted
12 if nums[left] <= nums[mid]:
13 # Left half is sorted
14 if nums[left] <= target < nums[mid]:
15 right = mid - 1
16 else:
17 left = mid + 1
18 else:
19 # Right half is sorted
20 if nums[mid] < target <= nums[right]:
21 left = mid + 1
22 else:
23 right = mid - 1
24
25 return -1
261class Solution {
2 public int search(int[] nums, int target) {
3 int left = 0;
4 int right = nums.length - 1;
5
6 while (left <= right) {
7 int mid = left + (right - left) / 2;
8
9 if (nums[mid] == target) {
10 return mid;
11 }
12
13 // Check which half is sorted
14 if (nums[left] <= nums[mid]) {
15 // Left half is sorted
16 if (nums[left] <= target && target < nums[mid]) {
17 right = mid - 1;
18 } else {
19 left = mid + 1;
20 }
21 } else {
22 // Right half is sorted
23 if (nums[mid] < target && target <= nums[right]) {
24 left = mid + 1;
25 } else {
26 right = mid - 1;
27 }
28 }
29 }
30
31 return -1;
32 }
33}
341class Solution {
2public:
3 int 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;
9
10 if (nums[mid] == target) {
11 return mid;
12 }
13
14 // Check which half is sorted
15 if (nums[left] <= nums[mid]) {
16 // Left half is sorted
17 if (nums[left] <= target && target < nums[mid]) {
18 right = mid - 1;
19 } else {
20 left = mid + 1;
21 }
22 } else {
23 // Right half is sorted
24 if (nums[mid] < target && target <= nums[right]) {
25 left = mid + 1;
26 } else {
27 right = mid - 1;
28 }
29 }
30 }
31
32 return -1;
33 }
34};
351function search(nums: number[], target: number): number {
2 let left = 0;
3 let right = nums.length - 1;
4
5 while (left <= right) {
6 const mid = Math.floor((left + right) / 2);
7
8 if (nums[mid] === target) {
9 return mid;
10 }
11
12 // Check which half is sorted
13 if (nums[left] <= nums[mid]) {
14 // Left half is sorted
15 if (nums[left] <= target && target < nums[mid]) {
16 right = mid - 1;
17 } else {
18 left = mid + 1;
19 }
20 } else {
21 // Right half is sorted
22 if (nums[mid] < target && target <= nums[right]) {
23 left = mid + 1;
24 } else {
25 right = mid - 1;
26 }
27 }
28 }
29
30 return -1;
31}
32Time and Space Complexity
Time Complexity: O(log n), where n is the length of the array nums. 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 left, right, and mid, 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. Forgetting to Check for Target Match First
The Pitfall:
Jumping directly into determining which half is sorted without first checking if nums[mid] == target.
Example of the Issue:
while left <= right: mid = (left + right) // 2 # Forgot to check nums[mid] == target! if nums[left] <= nums[mid]: ...
Solution: Always check for target match first:
if nums[mid] == target: return mid
2. Incorrect Boundary Comparison When Determining the Sorted Half
The Pitfall:
Using strict inequality (nums[left] < nums[mid]) instead of nums[left] <= nums[mid] when determining which half is sorted.
Example of the Issue:
if nums[left] < nums[mid]: # Wrong! Missing the equal case # Left half logic
Consider the array [3, 1] searching for target 1:
- With strict inequality:
3 < 3is false, incorrectly assuming right half is sorted - With correct comparison:
3 <= 3is true, correctly identifying left half pattern
Solution:
Always use <= to properly handle all cases:
if nums[left] <= nums[mid]: # Correct # Left half is sorted
3. Off-by-One Errors in Range Checks
The Pitfall: Using incorrect comparison operators when checking if target is in the sorted range.
Example of the Issue:
# Wrong: includes mid in the range check if nums[left] <= target <= nums[mid]: right = mid - 1
Since we already checked nums[mid] != target, including nums[mid] in the range means the condition is never satisfied when target == nums[mid].
Solution:
Use exclusive comparison for nums[mid]:
# Left half sorted: target must be >= left and < mid if nums[left] <= target < nums[mid]: right = mid - 1 # Right half sorted: target must be > mid and <= right if nums[mid] < target <= nums[right]: left = mid + 1
4. Confusing Which Half to Search
The Pitfall: Searching the wrong half when the target is not in the sorted range.
Example of the Issue:
if nums[left] <= nums[mid]: if nums[left] <= target < nums[mid]: right = mid - 1 # Forgot else case - infinite loop!
Solution: Always handle both cases for each sorted half:
if nums[left] <= nums[mid]: if nums[left] <= target < nums[mid]: right = mid - 1 else: left = mid + 1 # Target must be in the other half
Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?
Recommended Readings
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
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 If you prefer videos here's a video that explains recursion in a fun and easy way 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 him
Want a Structured Path to Master System Design Too? Don’t Miss This!