81. Search in Rotated Sorted Array II
Problem Description
In this problem, we are given an array nums
that has initially been sorted in non-decreasing order but then has been rotated around a pivot. The rotation means that nums
is rearranged such that elements to the right of the pivot (including the pivot) are moved to the beginning of the array, and the remaining elements are shifted rightward. This rearrangement maintains the order of both subsets but not the entire array. Our task is to determine if a given target
integer exists in nums
. We need to accomplish this in an efficient way, aiming to minimize the number of operations performed.
Intuition
The challenge lies in the fact that due to the array's rotation, it's not globally sorted anymore—although within the two subarrays created by the rotation (one before and one after the pivot), the elements remain sorted. We can leverage this sorted property to apply a modified binary search to achieve an efficient solution.
Since the array is only partially sorted, a regular binary search isn't directly applicable, but we can modify our approach to work with the rotation. The key idea is to perform regular binary search steps, but at each step, figure out which portion of the array is sorted and then decide whether the target
value lies within that sorted portion or the other portion.
We need to deal with the situation where the middle element is equal to the element on the right side of our searching range. This complicates things as it could represent the pivot point or a sequence of duplicate values. When such a case occurs, we can't make a definite decision about which part to discard, so we just shrink our search space by moving the right pointer one step left and continue the search.
By following this approach, we ensure that we are always halving our search space, leveraging the sorted nature of the subarrays whenever possible, and gradually converge towards the target element if it exists in the nums
array.
Learn more about Binary Search patterns.
Solution Approach
The algorithm uses a while-loop to repeatedly divide the search range in half, similar to a classic binary search, but with additional conditions to adapt it for the rotated array. The nums
array is not passed by value. Instead, pointers (l
and r
) representing the left and right bounds of the current search interval are used to track the search space within the array, which is a space-efficient approach that doesn't involve additional data structures.
Here's a step-by-step breakdown of the code:
- Set the initial
l
(left pointer) to0
andr
(right pointer) ton - 1
, wheren
is the length of thenums
array. - Start the
while
loop, which runs as long asl < r
. This means we continue searching as long as our search space contains more than one element. - Calculate the mid-point
mid
using the expression(l + r) >> 1
, which is equivalent to(l + r) / 2
but faster computationally as it uses bit shifting. - Three cases are compared to determine the next search space:
- Case 1: If
nums[mid] > nums[r]
, we know the left part fromnums[l]
tonums[mid]
must be sorted. We then check iftarget
lies within this sorted part. If it does, we narrow our search space to the left part by settingr
tomid
. Otherwise,target
must be in the right part, and we updatel
tomid + 1
. - Case 2: If
nums[mid] < nums[r]
, the right part fromnums[mid]
tonums[r]
is sorted. Iftarget
is within this sorted interval, we updatel
tomid + 1
to search in this part. Otherwise, we adjustr
to the left by assigning it tomid
. - Case 3: If
nums[mid] == nums[r]
, we can't determine the sorted part as the elements are equal, possibly due to duplicates. We can't discard any half, so we decreaser
by one to narrow down the search space in small steps.
- Case 1: If
- This process repeats, halving the search space each time until
l
equalsr
, at which point we exit the loop. - Check if the remaining element
nums[l]
is equal totarget
. If so, returntrue
, else returnfalse
.
This solution leverages the sorted subarray properties induced by rotation and the efficiency of the binary search while handling the duplicates gracefully. This ensures that we minimize the search space as quickly as possible and determine the existence of the target
in the array, thus decreasing the overall operation steps.
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 take a small example to illustrate the solution approach using the steps mentioned below:
Suppose nums
is [4, 5, 6, 7, 0, 1, 2]
and our target
is 0
. This array is sorted and then rotated at the pivot element 0
.
- Set the initial pointers:
l = 0
,r = 6
(since there are 7 elements). - The
while
loop begins becausel < r
(0 < 6
). - Calculate the mid-point:
mid = (l + r) >> 1
, hencemid = 3
. The element atmid
is7
. - Proceed with comparing the three cases:
- Case 1: Since
nums[mid]
(7) is greater thannums[r]
(2), we find that the left part fromnums[l]
tonums[mid]
is sorted. We check iftarget
(0) could be in this sorted part. Given the sorted array [4,5,6,7], we seetarget
is not there, sol = mid + 1
, which makesl = 4
. - The right bound
r
remains the same sincetarget
was not within the left sorted part.
- Case 1: Since
- Now
l = 4
andr = 6
. We again calculatemid = (4 + 6) >> 1
, somid = 5
. The element at mid is1
.- Case 2: Since
nums[mid]
(1) is less thannums[r]
(2), the right part is sorted ([1,2]). Thetarget
(0) is not within this interval, so we updater
tomid
which meansr = 5
- 1 = 4. - The left bound
l
remains at 4 because thetarget
was not located in the sorted right part.
- Case 2: Since
- We end up with
l = 4
andr = 4
as both are equal, which means we exit the loop. - Check the remaining element
nums[l]
, which isnums[4]
(0) againsttarget
(0). They match, so we would returntrue
.
Through this example, the solution narrows down the search space by binary search while considering the effects of rotation. This results in an efficient way to determine if the target
exists in the rotated sorted array, without having to search every element.
Solution Implementation
1from typing import List
2
3class Solution:
4 def search(self, nums: List[int], target: int) -> bool:
5 # The length of the input array
6 num_length = len(nums)
7
8 # Initialize the left and right pointers
9 left, right = 0, num_length - 1
10
11 # Binary search with modifications to handle the rotated sorted array
12 while left < right:
13 # Calculate the middle index
14 mid = (left + right) // 2
15
16 # If the middle element is greater than the rightmost element,
17 # it means the smallest element is to the right of mid.
18 if nums[mid] > nums[right]:
19 # Target is in the left sorted portion
20 if nums[left] <= target <= nums[mid]:
21 right = mid
22 else:
23 left = mid + 1
24 # If the middle element is less than the rightmost element,
25 # it means the smallest element is to the left of mid.
26 elif nums[mid] < nums[right]:
27 # Target is in the right sorted portion
28 if nums[mid] < target <= nums[right]:
29 left = mid + 1
30 else:
31 right = mid
32 # If the middle element is equal to the rightmost element,
33 # we can't determine the smallest element's position,
34 # so we reduce the search space by one from the right.
35 else:
36 right -= 1
37
38 # Final comparison to see if the target is at the left index
39 return nums[left] == target
40
1class Solution {
2 public boolean search(int[] nums, int target) {
3 int left = 0;
4 int right = nums.length - 1;
5
6 // Continue searching while the window is valid
7 while (left < right) {
8 int mid = left + (right - left) / 2; // Avoid potential overflow of (left + right)
9
10 // If middle element is greater than the rightmost element, the pivot is in the right half
11 if (nums[mid] > nums[right]) {
12 // If target lies within the left sorted portion
13 if (nums[left] <= target && target <= nums[mid]) {
14 right = mid; // Narrow down to left half
15 } else {
16 left = mid + 1; // Search in the right half
17 }
18 }
19
20 // If middle element is less than the rightmost element, the left half is sorted properly
21 else if (nums[mid] < nums[right]) {
22 // If target lies within the right sorted portion
23 if (nums[mid] < target && target <= nums[right]) {
24 left = mid + 1; // Narrow down to right half
25 } else {
26 right = mid; // Search in the left half
27 }
28 }
29
30 // If middle element equals the rightmost element, we can't determine the pivot
31 // so we reduce the search space by moving the right pointer one step to the left
32 else {
33 right--;
34 }
35 }
36
37 // After the loop ends, left == right,
38 // checking if we have found the target
39 return nums[left] == target;
40 }
41}
42
1class Solution {
2public:
3 bool search(vector<int>& nums, int target) {
4 // Initialize the start and end indices
5 int start = 0, end = static_cast<int>(nums.size()) - 1;
6
7 // While the search space is valid
8 while (start <= end) {
9 // Calculate the midpoint index
10 int mid = start + (end - start) / 2;
11
12 // Check if the middle element is the target
13 if (nums[mid] == target) {
14 return true;
15 }
16
17 // When middle element is greater than the last element, it means
18 // the left half is sorted correctly
19 if (nums[mid] > nums[end]) {
20 // Check if target is in the sorted half
21 if (target >= nums[start] && target < nums[mid]) {
22 end = mid - 1; // Narrow search to the left half
23 } else {
24 start = mid + 1; // Narrow search to the right half
25 }
26 // When middle element is less than the last element, it means
27 // the right half is sorted correctly
28 } else if (nums[mid] < nums[end]) {
29 // Check if target is in the sorted half
30 if (target > nums[mid] && target <= nums[end]) {
31 start = mid + 1; // Narrow search to the right half
32 } else {
33 end = mid - 1; // Narrow search to the left half
34 }
35 // When middle element is equal to the last element, we don't have enough
36 // information, thus reduce the size of search space from the end
37 } else {
38 end--;
39 }
40 }
41
42 // After the while loop, if we haven't returned true, then target isn't present
43 return false;
44 }
45};
46
1function search(nums: number[], target: number): boolean {
2 let left = 0;
3 let right = nums.length - 1;
4
5 // Iterate as long as the left pointer is less than the right pointer
6 while (left < right) {
7 // Calculate the mid-point index
8 const mid = Math.floor((left + right) / 2);
9
10 // If the middle element is greater than the element at right,
11 // the rotation is in the right half
12 if (nums[mid] > nums[right]) {
13 // Target is within the left sorted portion
14 if (nums[left] <= target && target <= nums[mid]) {
15 right = mid; // Narrow the search to the left half
16 } else {
17 left = mid + 1; // Narrow the search to the right half
18 }
19 // If the middle element is less than the element at right,
20 // the rotation is in the left half
21 } else if (nums[mid] < nums[right]) {
22 // Target is within the right sorted portion
23 if (nums[mid] < target && target <= nums[right]) {
24 left = mid + 1; // Narrow the search to the right half
25 } else {
26 right = mid; // Narrow the search to the left half
27 }
28 // If the middle element is equal to the element at right,
29 // we are not sure where the rotation is
30 } else {
31 // We decrease the right pointer by one
32 --right;
33 }
34 }
35
36 // After the loop, if the left element is the target, return true,
37 // otherwise, return false.
38 return nums[left] === target;
39}
40
Time and Space Complexity
Time Complexity
The time complexity of the given algorithm can primarily be considered as O(log n)
in the case of a typical binary search scenario without duplicates because the function repeatedly halves the size of the list it's searching. However, in the worst-case scenario where the list contains many duplicates which are all the same as the target, the algorithm degrades to O(n)
because the else
clause where r -= 1
could potentially be executed for a significant portion of the array before finding the target or determining it's not present.
Space Complexity
The space complexity of the code is O(1)
because it uses a fixed number of variables, regardless of the input size. No additional data structures are used that would depend on the size of the input array.
Learn more about how to find time and space complexity quickly using problem constraints.
Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?
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!