33. Search in Rotated Sorted Array
Problem Description
In this problem, we have an integer array nums
that is initially sorted in ascending order and contains distinct values. However, nums
might have been rotated at some unknown pivot index k
, causing the order of elements to change. After the rotation, the part of the array after the pivot index comes before the part of the array before the pivot index. Our goal is to find the index of a given integer target
in the rotated array. If the target
exists in the array, we should return its index; otherwise, return -1
.
Since the array is rotated, a standard binary search won't immediately work. We need to find a way to adapt binary search to work under these new conditions. The key observation is that although the entire array isn't sorted, one half of the array around the middle is guaranteed to be sorted.
The challenge is to perform this search efficiently, achieving a time complexity of O(log n)
, which strongly suggests that we should use binary search or a variation of it.
Intuition
The intuition behind the solution is to modify the binary search algorithm to handle the rotated array. We should focus on the property that the rotation splits the array into two sorted subarrays. When we calculate the middle element during the binary search, we can determine which part of the array is sorted: the part from the start to the middle or the part from the middle to the end. Once we know which part is sorted, we can see if the target
lies in that range. If it does, we adjust our search to stay within the sorted part. If not, we search in the other half.
To implement this, we have two pointers left
and right
that define the bounds of our search space. At each step, we compare the target
with the midpoint to decide on which half to continue our search. There are four cases to consider:
- If the
target
and the middle element both lie on the same side of the pivot (either before or after), we perform the standard binary search operation. - If the
target
is on the sorted side, but the middle element isn't, we search on the sorted side. - If the
target
is not on the sorted side, but the middle element is, we search on the side that includes the pivot. - If neither the
target
nor the middle element is on the sorted side, we again search on the side including the pivot.
By repeatedly narrowing down the search space and focusing on either the sorted subarray or the subarray containing the pivot, we can find the target
or conclude it's not present in O(log n)
time.
Learn more about Binary Search patterns.
Solution Approach
The solution implements a modified binary search algorithm to account for the rotated sorted array. Below is a step-by-step walkthrough of the algorithm as shown in the provided code snippet:
-
Initialize two pointers
left
andright
to represent the search space's bounds.left
starts at0
, andright
starts atn - 1
, wheren
is the length of the input arraynums
. -
The binary search begins by entering a
while
loop that continues as long asleft < right
, meaning there is more than one element in the search space. -
Calculate
mid
using(left + right) >> 1
. The expression>> 1
is equivalent to dividing by 2 but is faster, as it is a bitwise right shift operation. -
Determine which part of the array is sorted by checking if
nums[0] <= nums[mid]
. This condition shows that the elements fromnums[0]
tonums[mid]
are sorted in ascending order. -
Check if
target
is betweennums[0]
andnums[mid]
. If it is, that meanstarget
must be within this sorted portion, so adjustright
tomid
to narrow the search space to this sorted part. -
If the
target
is not in the sorted portion, it must be in the other half containing the rotation point. Updateleft
tomid + 1
to shift the search space to the right half of the array. -
If the sorted portion was the right half (
nums[mid] < nums[n - 1]
), check iftarget
is betweennums[mid]
andnums[n - 1]
. Adjust the search space accordingly by either movingleft
orright
depending if thetarget
is in the sorted portion or not. -
This loop continues until the search space is narrowed down to a single element.
-
After exiting the loop, check if
nums[left]
is thetarget
and returnleft
if that's the case, indicating that thetarget
is found at that index in the array. -
If the element at
nums[left]
is not thetarget
, return-1
to indicate thattarget
is not found in the array.
This binary search modification cleverly handles the rotation aspect by focusing on which part of the search space is sorted and adjusting the search bounds accordingly. No additional data structures are used, and the algorithm strictly follows an O(log n)
runtime complexity as required by the problem statement.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's illustrate the solution approach with a small example. Assume we have the rotated sorted array nums = [6, 7, 0, 1, 2, 4, 5]
and we are looking for the target
value 1
. The original sorted array before rotation might look something like [0, 1, 2, 4, 5, 6, 7]
, and in this case, the pivot is at index 2, where the value 0
is placed in the rotated array.
-
We initialize our search bounds, so
left = 0
andright = 6
(since there are 7 elements in the array). -
Start the binary search loop. Since
left < right
(0 < 6), we continue. -
Calculate the middle index,
mid = (left + right) >> 1
. This gives usmid = 3
. -
Check if the left side is sorted by checking if
nums[left] <= nums[mid]
(comparing6
with1
). It is false, so the left-half is not sorted, the right-half must be sorted. -
Since
1
(our target) is less than6
(the value atnums[left]
), we know the target is not in the left side. We now look into the right half since the rotation must have happened there. -
Update
left
tomid + 1
, which givesleft = 4
. -
Re-evaluate
mid
in the next iteration of the loop. Nowmid = (left + right) >> 1 = (4 + 6) >> 1 = 5
. The middle element at index 5 is4
. -
Check again where the sorted part is. Now
nums[left] <= nums[mid]
(1
<=4
) is true, which means we are now looking at the sorted part of the array. -
Check if the
target
is within the range ofnums[left]
andnums[mid]
. Here,1
is within1
to4
, so it must be within this range. -
Now we update
right
tomid
, settingright = 5
. -
Continue the loop. At this point,
left = 4
andright = 5
,mid = (4 + 5) >> 1 = 4
. The value atnums[mid]
is thetarget
we are looking for (1
). -
Since
nums[mid]
is equal to thetarget
we returnmid
, which is4
.
Using this approach, we successfully found the target
1
in the rotated sorted array and return the index 4
. This example demonstrates the modified binary search algorithm used within the rotated array context.
Solution Implementation
1class Solution:
2 def search(self, nums: List[int], target: int) -> int:
3 # Initialize the boundary indexes for the search
4 left, right = 0, len(nums) - 1
5
6 # Use binary search to find the target
7 while left < right:
8 # Calculate the middle index
9 mid = (left + right) // 2 # Updated to use floor division for clarity
10
11 # Determine if the mid element is in the rotated or sorted part
12 if nums[0] <= nums[mid]:
13 # If target is between the first element and mid, go left
14 if nums[0] <= target <= nums[mid]:
15 right = mid
16 # Else, go right
17 else:
18 left = mid + 1
19 else:
20 # If target is between mid and last element, go right
21 if nums[mid] < target <= nums[-1]: # Use -1 for last element index
22 left = mid + 1
23 # Else, go left
24 else:
25 right = mid
26
27 # Check if the left index matches the target, otherwise return -1
28 return left if nums[left] == target else -1
29
1class Solution {
2
3 public int search(int[] nums, int target) {
4 // Length of the array.
5 int arrayLength = nums.length;
6
7 // Initialize start and end pointers.
8 int start = 0, end = arrayLength - 1;
9
10 // Binary search algorithm to find target.
11 while (start < end) {
12 // Calculate middle index of the current segment.
13 int mid = (start + end) / 2;
14
15 // When middle element is on the non-rotated portion of the array.
16 if (nums[0] <= nums[mid]) {
17
18 // Check if the target is also on the non-rotated portion and adjust end accordingly.
19 if (nums[0] <= target && target <= nums[mid]) {
20 end = mid;
21 } else {
22 start = mid + 1;
23 }
24
25 // When middle element is on the rotated portion of the array.
26 } else {
27
28 // Check if the target is also on the rotated portion and adjust start accordingly.
29 if (nums[mid] < target && target <= nums[arrayLength - 1]) {
30 start = mid + 1;
31 } else {
32 end = mid;
33 }
34 }
35 }
36
37 // After narrowing down to one element, check if it's the target.
38 // If nums[start] is the target, return its index, otherwise return -1.
39 return nums[start] == target ? start : -1;
40 }
41
42}
43
1class Solution {
2public:
3 int search(vector<int>& nums, int target) {
4 // Initialize the size of the input vector
5 int size = nums.size();
6 // Define the initial search range
7 int left = 0, right = size - 1;
8
9 // Perform binary search
10 while (left < right) {
11 // Find the middle index of the current search range
12 int mid = left + (right - left) / 2; // Avoids potential overflow compared to (left + right) >> 1
13
14 // Determine the side of the rotated sequence 'mid' is on
15 if (nums[0] <= nums[mid]) {
16 // 'mid' is in the left (non-rotated) part of the array
17 if (nums[0] <= target && target <= nums[mid]) {
18 // Target is within the left (non-rotated) range, search left side
19 right = mid;
20 } else {
21 // Search right side
22 left = mid + 1;
23 }
24 } else {
25 // 'mid' is in the right (rotated) part of the array
26 if (nums[mid] < target && target <= nums[size - 1]) {
27 // Target is within the right (rotated) range, search right side
28 left = mid + 1;
29 } else {
30 // Search left side
31 right = mid;
32 }
33 }
34 }
35
36 // The final check to see if the target is found at 'left' index
37 return (left == right && nums[left] == target) ? left : -1;
38 }
39};
40
1function search(nums: number[], target: number): number {
2 const length = nums.length;
3 let leftIndex = 0;
4 let rightIndex = length - 1;
5
6 // Use binary search to find the target
7 while (leftIndex < rightIndex) {
8 // Calculate the middle index
9 const midIndex = Math.floor((leftIndex + rightIndex) / 2); // shifted to use Math.floor for clarity
10
11 // Check if the first element is less than or equal to the middle element
12 if (nums[0] <= nums[midIndex]) {
13 // If target is between the first element and middle element
14 if (nums[0] <= target && target <= nums[midIndex]) {
15 // Narrow down the right bound
16 rightIndex = midIndex;
17 } else {
18 // Target must be in the second half
19 leftIndex = midIndex + 1;
20 }
21 } else {
22 // If target is between the middle element and the last element
23 if (nums[midIndex] < target && target <= nums[length - 1]) {
24 // Narrow down the left bound
25 leftIndex = midIndex + 1;
26 } else {
27 // Target must be in the first half
28 rightIndex = midIndex;
29 }
30 }
31 }
32
33 // Check if we have found the target
34 return nums[leftIndex] == target ? leftIndex : -1;
35}
36
Time and Space Complexity
Time Complexity
The given code performs a binary search over an array. In each iteration of the while loop, the algorithm splits the array into half, investigating either the left or the right side. Since the size of the searchable section of the array is halved with each iteration of the loop, the time complexity of this operation is O(log n)
, where n
is the number of elements in the array nums
.
Space Complexity
The space complexity of the algorithm is O(1)
. The search is conducted in place, with only a constant amount of additional space needed for variables left
, right
, mid
, and n
, regardless of the size of the input array nums
.
Learn more about how to find time and space complexity quickly using problem constraints.
What's the output of running the following function using input 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
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
LeetCode 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 we
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!
The example walkthrough is a bit confusing, mostly because the index of the target value
1
in the rotated arraynums = [6, 7, 0, 1, 2, 4, 5]
is 3, not 4.