Find Minimum in Rotated Sorted Array

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
int findMin(vector<int> &num) {
        int start=0,end=num.size()-1;

        while (start<end) {
            if (num[start]<num[end])
                return num[start];

            int mid = (start+end)/2;

            if (num[mid]>=num[start]) {
                start = mid+1;
            } else {
                end = mid;
            }
        }

        return num[start];
    }
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
# @param {Integer[]} nums
# @return {Integer}
def find_min(nums)
    left = 0
    right = nums.size-1

    while left < right
        if nums[left] < nums[right]
            return nums[left]
        end
        mid = left + (right - left)/2

        if nums[mid] >= nums[left]
            left = mid + 1
        else
           right = mid 
        end
    end

    nums[left]
end

## Identifying Problem Isomorphism

"Find Minimum in Rotated Sorted Array" can be mapped to "Search in Rotated Sorted Array".

The premise for both problems is the same: You are given a sorted array that has been rotated at some pivot unknown to you beforehand. The difference lies in the goal of each problem.

In "Find Minimum in Rotated Sorted Array", the objective is to find the minimum element in the array, which will be the pivot of the rotation.

In "Search in Rotated Sorted Array", you have to return the index of a target element within the array.

Both problems require understanding of binary search and how it can be adapted for rotated arrays, making the concepts and techniques used in one applicable to the other.

"Find Minimum in Rotated Sorted Array" is simpler, as it only involves locating the minimum value. "Search in Rotated Sorted Array" adds an additional layer of complexity in that you must find a specific target value. As such, starting with the problem of finding the minimum could provide a good foundation for tackling the search problem.

## 10 Prerequisite LeetCode Problems

Here are 10 simpler problems than the "Find Minimum in Rotated Sorted Array" problem. They deal with binary search, sorting, and arrays:

1. **Binary Search (LeetCode #704):** This is the fundamental problem to get you familiar with the binary search.

2. **First Bad Version (LeetCode #278):** This problem introduces the concept of binary search in the context of a real-world application.

3. **Squares of a Sorted Array (LeetCode #977):** This problem helps to understand sorting and manipulating arrays.

4. **Two Sum II - Input array is sorted (LeetCode #167):** You can practice array manipulation and binary search concepts.

5. **Find Smallest Letter Greater Than Target (LeetCode #744):** This problem involves binary search in a slightly different scenario.

6. **Search Insert Position (LeetCode #35):** This problem provides a good base for binary search within a sorted array.

7. **Rotate Array (LeetCode #189):** This problem can help you understand the "rotation" aspect in the context of arrays.

8. **Single Element in a Sorted Array (LeetCode #540):** A slightly different twist on binary search, but it'll help solidify the concept.

9. **Find the Duplicate Number (LeetCode #287):** This problem can give a good understanding of how to handle duplicate values in an array.

10. **Maximum Subarray (LeetCode #53):** This is a good introduction to the concept of divide and conquer, which may help in more complex problems.

These cover the foundation in binary search, arrays, and sorting. After completing these problems, you should be better prepared to tackle the "Find Minimum in Rotated Sorted Array" problem.

## Problem Classification

This problem falls under the domain of data manipulation in arrays, and more specifically it deals with rotated sorted arrays. 

'What' components: 

1. We are given a sorted array that has been rotated between 1 and n times.
2. The rotation means that the smallest element could be placed at any index, and not necessarily at the beginning of the array.
3. The task is to find the minimum element in the array.


This problem can be classified as a search problem. Given the condition that the array is sorted but rotated, the optimal approach will likely be a variant of binary search due to the sorted nature of the original array. We are asked for a solution with time complexity O(log n), which further hints towards a binary search or similar divide-and-conquer strategy.


Binary search is typically used in problems that require searching in a sorted structure and the problem is asking for a logarithmic time complexity solution. Although the array has been rotated, the 'sorted' structure remains, making binary search a viable strategy. The challenge here is adapting the binary search to handle the rotation. In a typical binary search, we would examine the midpoint and determine whether the target value lies in the lower half or the upper half of the array. In this problem, because of the rotation, we have to decide not only which half of the array to search, but also whether to look at the high or low end of that half.

## Visual Model of the Problem

The problem can be visualized by considering how a sorted array changes when it's rotated. A rotation involves taking the last element of the array and moving it to the front, shifting all other elements to the right.

Let's take an example sorted array of unique integers:

Sorted array: [1, 2, 3, 4, 5, 6, 7]

If this array is rotated 3 times, it becomes:

Rotated array: [5, 6, 7, 1, 2, 3, 4]

In this problem, we're asked to find the smallest number in a rotated array. Despite the rotation, the sorted order of the array can still be seen as two ascending sequences. The pivot point of the rotation is the only place where the element to the right is smaller than the current element.

The problem becomes finding this pivot point in the rotated array, as the next element after this pivot is the smallest element.

The task is to find this minimum value, and with a strategy like binary search, we aim to do this efficiently in O(log n) time.

Given a sorted and rotated array like:

[5, 6, 7, 1, 2, 3, 4]

You could visualize it as such:

Index: 0 1 2 3 4 5 6 ————————- Value: | 5 | 6 | 7 | 1 | 2 | 3 | 4 | ————————-


In this problem, our goal is to find the minimum element (1). Despite the rotation, the original sorted structure of the array is still visible. We have two sorted subsequences: [5, 6, 7] and [1, 2, 3, 4].

The key is that the first element of the second subsequence is the smallest element in the array. Moreover, it is the only place in the array where the element to the right is less than the current element (in this case, 1 < 7). We call this point the pivot point, and it is the result of rotation.

So, the problem can be reframed to find this pivot point, which is the only place in the array where the right element is less than the current element.

I hope this gives a clearer visualization of the problem. To solve this problem in O(log n) time, you might think of a binary search approach to effectively find this pivot point.

## Problem Restatement

We are given an array that contains unique numbers and has two distinct characteristics:

1. The numbers are initially sorted in increasing order.
2. The sorted array is then "rotated". This rotation is not random; it involves moving the last element of the array to the front, and doing this operation anywhere from 1 to n times (where n is the size of the array). 

Our task is to find the smallest number in this rotated array.

Furthermore, the problem has a few constraints:

- The length of the array, n, is between 1 and 5000.
- The array contains unique numbers, which means there are no repeating numbers.
- The numbers in the array are between -5000 and 5000.
- The array is sorted in ascending order before rotation.
- The rotation occurs between 1 and n times.

The goal is not just to solve the problem, but to solve it efficiently - the solution needs to have a time complexity of O(log n).

In simpler words, we are given a list of unique numbers that have been shuffled in a specific way (by rotating), and we need to find the smallest number in the list quickly.

## Abstract Representation of the Problem

You are given a sequence (S) of n unique elements, initially sorted in ascending order. This sequence is then subjected to a defined transformation operation (T): elements from the end of the sequence are moved to the start of the sequence, with this operation repeated a certain number of times (between 1 and n times). The transformed sequence retains the property that it consists of two sorted sequences in ascending order.

The task is to find the smallest element in the transformed sequence, in an efficient manner - specifically in O(log n) time complexity.

This emphasizes the structure and key elements of the problem: the initial sorted sequence, the transformation operation, the properties of the transformed sequence, and the task to be performed on it, with a specific efficiency requirement. The specifics of the elements themselves, and the exact nature of the transformation operation, are not essential for this abstract representation.

## Terminology

There are several technical terms and concepts that are crucial to understanding this problem:

1. **Array**: An array is a data structure that stores a collection of elements, each identified by an index or key. In this problem, the array is used to store the initial sorted sequence of numbers.

2. **Rotation**: Rotation in the context of an array typically refers to moving elements from one end of the array to the other. In this problem, we're dealing with a specific type of rotation where elements from the end of the array are moved to the start.

3. **Sorted Array**: A sorted array is an array in which the elements are in a specific order. In this case, the initial array is sorted in ascending order. This property changes after the array is rotated.

4. **Binary Search**: Binary search is an efficient algorithm for finding an item in a sorted list of items. It works by repeatedly dividing in half the portion of the list that could contain the item, until the possible locations are narrowed down to just one. In the context of this problem, we need to use a variant of binary search to find the minimum element efficiently in the rotated sorted array.

5. **Time Complexity**: Time complexity of an algorithm quantifies the amount of time taken by an algorithm to run as a function of the size of the input to the program. The problem requires the solution to have a time complexity of O(log n), where n is the size of the array, which indicates that the solution should be quite efficient.

6. **Unique elements**: In the context of this problem, this means that each element in the array appears exactly once.

Understanding these terms and concepts is crucial to both comprehending the problem statement and working towards a solution.

## Problem Simplification and Explanation

Imagine you have a book with numbered pages in correct order from 1 to n. The book represents our array and each page represents an element in the array. Now, imagine that someone took a bunch of pages from the back of the book and moved them to the front. That's what the rotation operation does to our array.

Despite this reshuffling, if you look closely, you will notice that the book (or our array) still has a pattern: it has two sections, both in increasing order. One section is from the first page (or the first element of the array) to the last page before the reshuffling began, and the other section is from the first page of the reshuffled pages to the end of the book. 

Now, your task is to find the page with the smallest number (or the smallest element in the array). You know that it will be the first page of the second section - because the pages were initially in increasing order before the reshuffle, the smallest page number will be found right after the highest page number. The challenge is to find this page as quickly as possible.

Key concepts and their interaction:

- **Array**: Our book. It is a collection of elements, or in our case, pages. Each page has a unique number, and the book was initially in increasing order.

- **Rotation**: The reshuffling of the pages. A number of pages from the back of the book are moved to the front, creating two sections that are still in increasing order.

- **Binary search**: An efficient way to find a specific page. By checking the middle page of the book and comparing its number with the first and the last page, you can tell which half of the book the smallest page number lies in and then repeat the process with that half. This way, you don't have to check every single page (or element), which makes the search process more efficient.

The goal is to use binary search to find the smallest element in the array (or the smallest page number in the book) as quickly as possible.

## Constraints

Given the problem statement and constraints, there are indeed several characteristics that can be exploited to devise an efficient solution:

1. **Unique integers**: The fact that all the integers in the array are unique means there is a definite order without any repeating numbers. This simplifies our search as we don't need to account for duplicate entries which might complicate the binary search approach.

2. **Array is sorted**: Although the array is rotated, the array is initially sorted in ascending order. This means that we can take advantage of properties of sorted arrays, such as the ability to use binary search to quickly locate elements.

3. **Array is rotated**: The rotation of the array creates a specific pattern in the array — it consists of two sorted (ascending) subarrays. The smallest element in the array will be the first element of the second subarray.

4. **Boundaries on the array size and elements**: The constraints given for the size of the array (1 <= n <= 5000) and the range of elements (-5000 <= nums[i] <= 5000) help to define the problem space. However, they don't seem to provide any exploitable advantage for this particular problem other than knowing the limits of input size.

5. **Requirement of O(log n) time complexity**: This constraint actually helps us in determining the possible algorithms to solve this problem. With the requirement of logarithmic time complexity, we can immediately think of binary search or other divide-and-conquer strategies, which have a time complexity of O(log n) and work well with sorted arrays (or partially sorted in this case).

By observing these characteristics, we can devise an efficient approach to solve the problem. Specifically, a modified binary search can be utilized to find the minimum element in the rotated sorted array, taking advantage of the unique and sorted but rotated nature of the array.

The constraints in the problem provide important insights that can guide us toward an optimal solution:

1. **Size of the array (1 <= n <= 5000)**: The array size isn't massive, but it's also not tiny. Algorithms with time complexity of O(n) or worse could potentially become problematic for the maximum size. This points us towards solutions with better time complexity, ideally O(log n).

2. **Elements are unique**: This simplifies the problem since we don't need to worry about handling duplicates, which can complicate many algorithms, particularly search algorithms.

3. **Array is initially sorted and then rotated**: This tells us that we can't simply perform a binary search for the smallest element, because while the array is sorted, it's also been rotated, breaking the normal ascending order. However, this also provides us with valuable information - that the array is composed of two sorted subarrays.

4. **Range of numbers (-5000 <= nums[i] <= 5000)**: Knowing the range of numbers can sometimes help in formulating the solution but in this case, it doesn't seem to give any additional advantage.

5. **Requirement of O(log n) time complexity**: This is a significant clue in determining the approach to solve the problem. It indicates that a logarithmic time complexity algorithm, such as binary search or a similar divide-and-conquer approach, will likely be involved in the optimal solution.

These insights gleaned from the constraints should help guide our approach to solving the problem. For this problem, a modified binary search algorithm seems to be the promising approach that satisfies the constraints.

## Case Analysis

Let's walk through additional examples and edge cases that might help understand the problem better:

1. **Smallest possible array**:
    - Input: nums = [1]
    - Output: 1
    - Explanation: In this case, the array only contains a single element. Regardless of how many times it's rotated, the smallest element will always be the only element present. This case tests the lower limit of the size constraint.

2. **Array of two elements**:
    - Input: nums = [2,1]
    - Output: 1
    - Explanation: With an array of two elements, the array can either not be rotated (in which case the smallest element is the first one), or it can be rotated once (in which case the smallest element is now the second one). This case tests the function with the smallest non-trivial input size.

3. **Array where the smallest element is the first element**:
    - Input: nums = [1,2,3,4,5]
    - Output: 1
    - Explanation: Even though the array has been rotated (in this case, 5 times), the smallest element is still the first element in the array. This highlights that the rotation doesn't necessarily move the smallest element.

4. **Array where the smallest element is in the middle**:
    - Input: nums = [4,5,1,2,3]
    - Output: 1
    - Explanation: In this case, the smallest element is neither at the beginning nor the end of the array. This highlights that the smallest element can appear anywhere in the array after rotation.

5. **Array with negative numbers**:
    - Input: nums = [3,-1,1,2]
    - Output: -1
    - Explanation: This case illustrates that the array can contain negative numbers, and the smallest number could be a negative number.

6. **Largest possible array with maximum and minimum possible values**:
    - Input: nums = [5000, -5000, -4999, ..., 4998, 4999] (array of size 5000, rotated once)
    - Output: -5000
    - Explanation: This case tests the function's performance with the largest possible input size and the full range of possible element values. 

These cases collectively cover a wide range of possibilities and edge cases, which should help in generating a more robust solution.

## Identification of Applicable Theoretical Concepts

There are a few mathematical and algorithmic concepts that can be applied to simplify this problem:

1. **Binary Search**: This problem essentially asks us to find the smallest element in a sorted and rotated array. While binary search is typically used to find a specific element in a sorted array, it can be modified to solve this problem efficiently. In a normal sorted array, the elements to the left of the middle are always smaller. However, in a rotated sorted array, this property does not hold. If the middle element is greater than the first element, then the left half is sorted. Otherwise, the right half is sorted. By determining which half is sorted, we can then decide which half the smallest element resides in.

2. **Divide and Conquer**: This is an overarching strategy that can be applied to solve the problem. We can keep dividing the array into two halves and narrow down the range where the smallest element can exist, thereby simplifying the problem.

3. **Properties of Sorted Arrays**: Despite the rotation, the array maintains some characteristics of a sorted array, namely that it is composed of two sorted subarrays. This property can be exploited to apply a modified version of binary search.

4. **Time Complexity Analysis**: The requirement of O(log n) time complexity suggests that an efficient algorithm is needed. Understanding how to analyze time complexity can guide us to ensure that the solution is efficient and meets the problem constraints.

By applying these concepts, we can effectively manage the problem and find an efficient solution. It's also worth noting that problems like this often rely heavily on understanding the nature of the data structure in question (in this case, an array) and the transformations that have been applied to it (in this case, sorting and rotating).

## Problem Breakdown and Solution Methodology

The main approach we will use is a modified version of binary search. The modifications come in to handle the fact that our array is not entirely sorted, but instead is a rotation of a sorted array. 

Here's a breakdown of the steps:

1. **Step 1: Set Initial Pointers**
   We start by setting two pointers, `low` and `high`, at the beginning and the end of the array respectively.

2. **Step 2: Enter the Binary Search Loop**
   We then enter a loop which will continue until `low` is less than `high`. This is our modified binary search loop.

3. **Step 3: Find the Midpoint**
   Inside this loop, the first thing we do is compute the mid-index of the current subarray bounded by `low` and `high`. This is calculated as `mid = low + (high - low) / 2`. Note that we use `(high - low) / 2` instead of `n / 2` to avoid possible integer overflow.

4. **Step 4: Compare and Adjust Pointers**
   We then compare the element at the `mid` index with the element at the `high` index. There are two scenarios to consider:
   
   - Scenario A: If the element at `mid` is less than or equal to the element at `high`, this means that the smallest element is to the left of `mid` (including `mid` itself), so we move the `high` pointer to `mid`. In other words, we update `high` to `mid`.

   - Scenario B: If the element at `mid` is greater than the element at `high`, this means that the smallest element is to the right of `mid`, so we move the `low` pointer to `mid + 1`. In other words, we update `low` to `mid + 1`.

5. **Step 5: Return the Smallest Element**
   Once the `low` pointer is not less than `high`, we break the loop and return the element at the `low` index, which is the minimum element in the rotated sorted array.

Let's visualize this process with a metaphor. Imagine you're playing a game of hide and seek, where the person hiding (the smallest number) is somewhere in a circular park (our rotated array). The park is composed of numbered stones, increasing from where the person is hiding and wrapping around. You're at the entrance of the park and you want to find the hiding person as quickly as possible. Your strategy is to continually select a point halfway between your current search boundaries, then decide which half to keep searching based on whether the middle point is higher or lower than the right boundary.

Let's go through an example:

Input: nums = [4,5,6,7,0,1,2]

- Step 1: `low` = 0, `high` = 6
- Step 2: Enter the loop.
- Step 3: Calculate `mid` = 0 + (6 - 0) / 2 = 3.
- Step 4: nums[3] = 7 > nums[6] = 2. So, we update `low` = `mid` + 1 = 4.
- Repeat steps 2-4: now `low` = 4, `high` = 6, `mid` = 4 + (6 - 4) / 2 = 5.
- nums[5] = 1 < nums[6] = 2, so we update `high` = `

mid` = 5.
- Repeat steps 2-4: now `low` = 4, `high` = 5, `mid` = 4 + (5 - 4) / 2 = 4.
- nums[4] = 0 < nums[5] = 1, so we update `high` = `mid` = 4.
- Now `low` = `high`, so we break the loop.
- Step 5: Return nums[4] = 0, which is the smallest element.

This approach is efficient and will always find the smallest element in O(log n) time, as required by the problem constraints.

If the parameters of the problem change, like if the array is not rotated or the array is not sorted, then this solution might not work. If the array is sorted but not rotated, the smallest element will always be the first element, and if the array is neither sorted nor rotated, then we would need to use a different approach to find the smallest element.

## Inference of Problem-Solving Approach from the Problem Statement

The problem statement mentions that we have a "sorted and rotated" array, and we're asked to find the "minimum element" in it. These are significant clues that we can apply a binary search strategy, which is a specific form of divide-and-conquer algorithm.

Here's why:

1. **Binary Search**: Binary search is an algorithm that works efficiently on sorted data structures to find a specific value. It continually divides the data set in half until the desired value is found, hence it follows a divide-and-conquer approach. The "sorted" nature of the array, even though it's rotated, allows us to use this technique effectively.

2. **Finding the Minimum Element**: The task to find the minimum element hints towards a comparison-based solution, which is another indication that binary search might be useful.

3. **Rotated Sorted Array**: In a rotated sorted array, there's a point where the order of the elements changes from a descending to an ascending sequence. This shift indicates that the array is composed of two sorted subarrays, which allows us to apply a modified form of binary search. By determining which half is sorted, we can then decide which half the smallest element resides in.

4. **Time Complexity**: The problem asks for a solution that runs in O(log n) time. This complexity suggests a solution that cuts down the problem size with each step, which is a characteristic of divide-and-conquer strategies and binary search specifically.

So, while the problem doesn't explicitly say "use binary search" or "use a divide-and-conquer strategy", the characteristics of the problem and the requirements of the problem statement lead us to infer that these techniques can be applied effectively.

## Stepwise Refinement

Here is the refined approach, broken down into steps:

1. **Initialize Pointers**: Set up two pointers, `low` and `high`, which point to the first and last elements of the array respectively.

2. **Binary Search Loop**: Start a loop that continues as long as `low` is less than `high`. This will be the main body of our binary search approach.

3. **Midpoint Calculation**: Within the loop, calculate the mid-index of the current range defined by `low` and `high`. This can be done with the formula `mid = low + (high - low) / 2`.

4. **Condition Check and Pointer Adjustment**: Now, compare the elements at the `mid` and `high` indices of the array:

   - If `nums[mid] <= nums[high]`, it means that the minimum element lies to the left of `mid` (including `mid` itself). Therefore, we update `high` to `mid`.

   - If `nums[mid] > nums[high]`, it indicates that the minimum element lies to the right of `mid`. Hence, update `low` to `mid + 1`.

5. **Result Return**: Once the loop condition breaks (i.e., `low` is not less than `high`), return the element at the `low` index. This is our minimum element.

In Python, this approach would be implemented as:

```python
def findMin(nums):
    low, high = 0, len(nums) - 1

    while low < high:
        mid = low + (high - low) // 2
        if nums[mid] <= nums[high]:
            high = mid
        else:
            low = mid + 1

    return nums[low]

This code will efficiently find the minimum element in a rotated sorted array in O(log n) time complexity and O(1) space complexity, as per the requirements of the problem statement. It works by repeatedly dividing the search range until the smallest element is found.

Here’s how we can break down the high-level solution into granular, actionable steps:

  1. Initialize Variables: Create two variables named low and high. Assign low the value of 0 (the first index) and high the value of len(nums) - 1 (the last index of the array).

  2. Start the Loop: Set up a while loop that continues until low is less than high. This loop will iterate over the array elements and narrow down the search space.

  3. Calculate the Midpoint: Within the loop, calculate the midpoint index. The midpoint is the integer value of (low + high) / 2.

  4. Compare Mid and High Elements: Inside the loop, create a conditional branch where you check if the element at the midpoint is less than or equal to the element at high. This is to determine which half of the array the minimum element is in.

  5. Update High or Low Index: If the midpoint element is less than or equal to the high element, update high to mid. If the mid element is greater than the high element, update low to mid + 1.

  6. Return the Minimum Element: After the loop finishes, low and high should point to the same location - the minimum element in the array. Return nums[low] as the smallest element in the array.

  7. Test Your Code: After writing the code, run it with multiple test cases to ensure it works as expected. Remember to test edge cases where the input array has one element, or is already sorted, or is sorted in reverse. These will help validate your code handles all possible scenarios.

The nature of this problem doesn’t naturally lend itself to being divided into smaller problems that can be solved independently. This is because it’s fundamentally a single task: finding the minimum element in a rotated sorted array.

However, certain parts of the approach can be understood independently:

  1. Understanding Binary Search: Binary search is a fundamental algorithm that you can study independently. It’s used in a wide range of problems and is essential to solving this one. Once you understand how to perform a binary search, you can apply it to this problem.

  2. Comparing Mid and High: The comparison of the mid and high elements can be understood separately. It’s key to understand why we compare these two elements and how that helps us to narrow down the search space.

  3. Understanding Array Rotation: The concept of rotating an array and its impact on the array’s properties can also be studied as an independent problem. Understanding how rotation affects a sorted array’s properties can provide insight into how to find the minimum element.

  4. Handling Edge Cases: Understanding how to handle edge cases (like when the array has only one element or when the array is already sorted) can be addressed independently, and is crucial to make sure the solution is robust and complete.

While each of these components can be studied and understood independently, to solve the problem, they all need to come together in a coordinated way.

There are a few repeatable patterns in our solution. Here are a couple of the most notable ones:

  1. Binary Search Pattern: The most obvious repeatable pattern in the solution is the structure of the binary search. This includes initializing low and high pointers, calculating a mid point, and then updating either the low or high pointers based on a condition. This pattern is very common in problems involving sorted arrays or when you need to find a specific item in O(log n) time.

  2. Pointer Manipulation: The process of adjusting the low and high pointers based on the comparison of elements at mid and high is a repeatable pattern that we see in many divide-and-conquer strategies. If the condition holds, we update one pointer; otherwise, we update the other. This pattern helps in narrowing down the search space.

  3. Loop until Condition: The loop structure while low < high is another common pattern. In this case, it continues until the search space is narrowed down to a single element. It’s used often when an iterative process needs to continue until a specific condition is met.

These patterns are not unique to this problem, but are common in many algorithmic problems, particularly those involving search or sorting operations on arrays or other sequential data structures.

Solution Approach and Analysis

Let’s break down the problem and the solution approach:

The problem is asking us to find the minimum element in a sorted array that has been rotated. The array was originally sorted in increasing order, and all elements are unique. The rotation means some elements from the start of the array have been moved to the end, but the order of elements is otherwise preserved. The challenge is to find the minimum element in O(log n) time.

You can think of this rotation operation like a clock face. Imagine the numbers 1 to 12 in order. If you rotate the numbers 4 positions, you’d get 5,6,7,8,9,10,11,12,1,2,3,4. The smallest number is now at the 9th position.

Now, let’s solve the problem:

  1. Setup: We will utilize the Binary Search method. It’s like trying to find a specific page in a thick book. Instead of going through every page, you’d open the book around the middle, and depending on whether the desired page is before or after, you’d repeat the process with the appropriate half of the book. So, we start by setting low to the start of the array and high to the end.

  2. Find the Midpoint: In each iteration, we calculate mid, the midpoint of low and high.

  3. Compare and Update Pointers: We compare the elements at the mid and high positions:

    • If the mid element is less than or equal to the high element, it means the minimum must be in the left half (including mid), so we move high to mid.
    • If the mid element is greater than the high element, the minimum must be in the right half, so we move low to mid + 1.
  4. Loop Until Found: We repeat steps 2 and 3 until low is not less than high, which means we’ve found the smallest element. The loop runs in O(log n) time because we halve the search space in each iteration.

  5. Return the Result: We return the element at low (or high as they will be the same), which is the minimum element in the rotated sorted array.

Let’s use an example to demonstrate how the algorithm works:

Example: nums = [4,5,6,7,0,1,2]

  • Initial state: low = 0, high = 7, mid = 3
  • nums[mid] > nums[high] -> The minimum is in the right half. Set low to mid + 1.
  • Next state: low = 4, high = 7, mid = 5
  • nums[mid] <= nums[high] -> The minimum is in the left half. Set high to mid.
  • Next state: low = 4, high = 5, mid = 4
  • nums[mid] <= nums[high] -> The minimum is in the left half. Set high to mid.
  • Next state: low = 4, high = 4
  • low is not less than high, so we stop and return nums[low], which is 0.

By using this approach, we’ve efficiently found the minimum element in the rotated sorted array.

Thought Process

The main cues from the problem statement are:

  1. The input is an array that is sorted and then rotated. This implies that the array has an order, but it has been shifted.
  2. We’re asked to find the minimum element. Given that the array was initially sorted, this would be a simple task. However, the rotation complicates this.
  3. The problem demands a solution that runs in O(log n) time, which suggests a binary search or a similar divide-and-conquer approach.

These cues suggest that a binary search algorithm would be a good fit because binary search works well with ordered datasets and has a time complexity of O(log n), meeting the requirement. However, the rotation of the array means that a traditional binary search won’t work - we need to modify it to account for the rotation.

The key insight here is understanding that despite the rotation, one half of the array (either left or right of the midpoint) will always remain sorted. By comparing the middle element with the rightmost one, we can determine which half is sorted and subsequently, which half the minimum element lies in. This process is repeated until the minimum element is found.

Here is the Python3 code implementing the above approach:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
def find_min(nums):
    if not nums:
        return -1

    low, high = 0, len(nums) - 1

    while low < high:
        mid = low + (high - low) // 2

        if nums[mid] <= nums[high]:  # Right half is sorted
            high = mid
        else:  # Left half is sorted, minimum must be in right half
            low = mid + 1

    return nums[low]

# Testing the function with the given examples:
print(find_min([3,4,5,1,2]))  # Expected output: 1
print(find_min([4,5,6,7,0,1,2]))  # Expected output: 0
print(find_min([11,13,15,17]))  # Expected output: 11

This function takes an input array nums and returns the minimum element. The algorithm follows the process we outlined earlier, using a binary search strategy and gradually narrowing down the search space until the minimum element is found.

From Brute Force to Optimal Solution

Let’s first discuss a brute force solution for this problem.

Brute Force Solution

The most straightforward approach to find the minimum element in a sorted and rotated array is to scan through the entire array and keep track of the smallest element found. Here’s how we could do that in Python:

1
2
def find_min(nums):
    return min(nums)

Or with a manual loop:

1
2
3
4
5
6
def find_min(nums):
    min_val = nums[0]
    for num in nums:
        if num < min_val:
            min_val = num
    return min_val

This brute force approach is quite simple, but it has a time complexity of O(n) because it examines every element in the array. For a small array, this isn’t a big problem, but as the array size grows, the time to find the minimum also grows linearly.

Optimized Solution

Given that the array was originally sorted before rotation, we can use this property to find a more efficient solution. Even though the array has been rotated, one half of the array will always be in sorted order. We can leverage this property to apply a binary search method, which has a time complexity of O(log n), much better than the O(n) of the brute force approach.

The optimized solution follows these steps:

  1. Set two pointers low and high at the start and end of the array.
  2. Calculate mid as the index in the middle.
  3. If the mid element is less than or equal to the high element, then the right half is sorted. So, set high to mid.
  4. If the mid element is greater than the high element, then the left half is sorted, and the minimum must be in the right half. So, set low to mid + 1.
  5. Repeat this process until low is not less than high. The smallest element is nums[low].

Here’s the Python code for the optimized solution:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Solution:
    def findMin(self, nums: List[int]) -> int:
        low, high = 0, len(nums) - 1

        while low < high:
            mid = low + (high - low) // 2

            if nums[mid] <= nums[high]:
                high = mid
            else:
                low = mid + 1

        return nums[low]				

This optimized solution has a time complexity of O(log n), which is a significant improvement over the brute force solution. The space complexity is O(1) because it only uses a constant amount of extra space, regardless of the size of the input array. Therefore, this optimized solution is much more efficient and scalable than the brute force approach.

Coding Constructs

Consider the following piece of complex software code.

  1. What are the high-level problem-solving strategies or techniques being used by this code?

  2. If you had to explain the purpose of this code to a non-programmer, what would you say?

  3. Can you identify the logical elements or constructs used in this code, independent of any programming language?

  4. Could you describe the algorithmic approach used by this code in plain English?

  5. What are the key steps or operations this code is performing on the input data, and why?

  6. Can you identify the algorithmic patterns or strategies used by this code, irrespective of the specific programming language syntax?

Language Agnostic Coding Drills

Your mission is to deconstruct this code into the smallest possible learning units, each corresponding to a separate coding concept. Consider these concepts as unique coding drills that can be individually implemented and later assembled into the final solution.

  1. Dissect the code and identify each distinct concept it contains. Remember, this process should be language-agnostic and generally applicable to most modern programming languages.

  2. Once you’ve identified these coding concepts or drills, list them out in order of increasing difficulty. Provide a brief description of each concept and why it is classified at its particular difficulty level.

  3. Next, describe the problem-solving approach that would lead from the problem statement to the final solution. Think about how each of these coding drills contributes to the overall solution. Elucidate the step-by-step process involved in using these drills to solve the problem. Please refrain from writing any actual code; we’re focusing on understanding the process and strategy.

Targeted Drills in Python

Now that you’ve identified and ordered the coding concepts from a complex software code in the previous exercise, let’s focus on creating Python-based coding drills for each of those concepts.

  1. Begin by writing a separate piece of Python code that encapsulates each identified concept. These individual drills should illustrate how to implement each concept in Python. Please ensure that these are suitable even for those with a basic understanding of Python.

  2. In addition to the general concepts, identify and write coding drills for any problem-specific concepts that might be needed to create a solution. Describe why these drills are essential for our problem.

  3. Once all drills have been coded, describe how these pieces can be integrated together in the right order to solve the initial problem. Each drill should contribute to building up to the final solution.

Remember, the goal is to not only to write these drills but also to ensure that they can be cohesively assembled into one comprehensive solution.

Q&A

The main idea is, the element is said to be minimum in the rotated sorted array if the previous element to it is greater than it or there is no previous element(i.e. no rotation). We can do this using Binary search

Find the mid element i.e. mid = (low+high)/2 If the (mid+1)th element is less than mid element then return (mid+1)th element If the mid element is less than (mid-1)th element then return the mid element If the last element is greater than mid element then search in left half If the last element is less than mid element then search in right half

Yes, you have understood the concept correctly. The minimum element in a rotated sorted array is the only element for which the previous element is larger, or if there is no previous element (in the case of zero rotations).

We can indeed find this using binary search with a slight modification. In this case, we are not searching for a specific key, but a specific condition (i.e., an element is smaller than its previous element). We adjust our search space based on the comparison of the middle element with the rightmost element.

The Python3 code for this approach is:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
def find_min(nums):
    low, high = 0, len(nums) - 1

    while low < high:
        mid = low + (high - low) // 2

        # Condition to find the pivot
        if mid > 0 and nums[mid] < nums[mid - 1]:
            return nums[mid]

        # If right half is sorted, go left
        if nums[mid] <= nums[high]:
            high = mid - 1
        else:  # go right
            low = mid + 1

    return nums[low]

Here are the steps:

  1. Start with two pointers: low at the start of the array and high at the end of the array.
  2. Compute the middle index, mid.
  3. Check if the element at mid is less than the element at mid - 1. If true, we have found our pivot (smallest element), return it.
  4. If the element at mid is less than the element at high, this means the right half is sorted and the pivot is not in this half. We then search in the left half, reducing the high to mid - 1.
  5. If the element at mid is not less than the element at high, the pivot is in the right half. So, search in the right half by moving low to mid + 1.
  6. If the loop ends without finding the pivot, this means the array wasn’t rotated, so the smallest element is nums[0].

This approach uses the binary search concept, yielding a time complexity of O(log n).

My code just: “sort(nums.begin(),nums.end()); return nums[0]”. It beats 100% in CPP.

The key is when an array is pre-sorted, sort algorithm can almost reach its best case, no longer NlogN !!

Same story in problem when they ask you to Merage K sorted array, Instead of using priority queue, just concatenate them and do a fast sort, and that beats 92.26% cpp files. In addition, let say those K array are in extremely bad case, where K is huge, each array only has 1 or 2 elements and the arrays are put into worst order. The performance of concatenating and sorting of course will drop a lot yet so will priority queue, and you need a lot of extra memory to keep your queue alive as well.

While it’s true that in some cases simply sorting the array and then returning the first element will work and may even outperform other solutions in terms of execution time, it’s important to note that this is not the most optimal solution from an algorithmic complexity perspective.

Your approach has a time complexity of O(n log n) because that’s the best time complexity that comparison-based sorting algorithms can achieve. However, the binary search method has a time complexity of O(log n), which is better.

Remember that the performance of an algorithm is not just about how fast it runs on a specific machine or for a specific test case. It’s also about how the time or space requirements grow as the size of the input increases. An algorithm with lower time complexity will outperform an algorithm with higher time complexity for sufficiently large inputs.

About the merging K sorted arrays problem, while sorting after concatenating the arrays will indeed work, using a priority queue or a heap to merge the arrays one by one will generally have better time complexity, especially when the length of the arrays (N) is much larger than the number of arrays (K).

The time complexity for the concatenating and sorting method is O(NK log NK), while using a priority queue gives a time complexity of O(NK log K), which is more efficient if N » K. The space complexity is O(K) for the priority queue, which is needed to keep track of the current elements in each array.

In conclusion, while some simpler methods might work well and even run faster in some cases, it’s important to understand the underlying time and space complexities to choose the most efficient method for the general case. The cases where you get fast times with sorting methods might be due to the specific implementation of the sort function in the language library, which is highly optimized and may take advantage of specific properties of the data or the hardware.

The problem requires O(log n) time so that already precludes sorting.

That’s correct! The problem statement explicitly asks for an algorithm that runs in O(log n) time. While sorting the array might provide a correct solution, it would not meet the time complexity requirement of the problem because the best time complexity we can achieve with comparison-based sorting algorithms is O(n log n), which is greater than O(log n).

In contrast, the modified binary search solution does meet the time complexity requirement because it runs in O(log n) time. It’s a more efficient solution because it takes advantage of the fact that the array is sorted and then rotated, rather than treating the input as a generic array and sorting it. It’s always beneficial to use the characteristics of the input data to our advantage when devising algorithms, as it can lead to more efficient solutions.

Therefore, while it’s sometimes possible to get a correct solution using a simpler or more straightforward method (like sorting), it’s not always the most efficient solution, especially in terms of time and space complexity. In competitive programming and technical interviews, it’s often crucial to find not just a correct solution, but the most efficient one that meets the problem’s requirements and constraints.

best case sorting time is still O(N) so you’re still better of just finding the smallest element linearly

Absolutely right! If we didn’t have any restrictions on time complexity and just needed to find the minimum element in an array, we could simply iterate through the array linearly, keep track of the smallest element we’ve seen so far, and return that. This operation would take O(n) time, which is optimal for this particular operation because we have to look at every element at least once to ensure we’ve found the smallest one.

However, in the context of this problem, we are specifically asked to find a solution that runs in O(log n) time. This requirement encourages us to take advantage of the fact that the array is sorted and then rotated. This property of the input allows us to apply a modified binary search approach, which can find the minimum element in O(log n) time.

So while a linear scan is usually the most efficient way to find the minimum in an unsorted array, the specific properties and requirements of this problem allow us to find a more efficient solution. This is a good example of how understanding the properties of the input data and the problem requirements can lead us to more efficient algorithms.

This problem in particular is one of those rare cases when the most intuitive solution (that seems like brute force in the beginning) is the one that gives the best answer somehow

I agree with you. In some problems, the brute-force solution is not only the most intuitive but also the most efficient, given the specific requirements of the problem. It might not be a “trick” or “clever” solution, but it works efficiently.

In this specific problem, the intuitive approach of linearly scanning the array for the minimum element does not meet the O(log n) time complexity requirement. Here, the binary search approach is the way to meet this specific requirement.

It’s indeed one of the beauties of computer science and problem-solving, where there isn’t always a single “right” way to solve a problem. Different approaches can be more or less efficient depending on the specific requirements and constraints of the problem.

In a practical scenario or in the real world, the best solution often depends on a balance of various factors including time complexity, space complexity, code readability, and maintainability. Sometimes a more straightforward and readable code can be more preferable than a more complex one, even if the latter is more efficient in terms of time and space complexity, especially if the efficiency gains are marginal.

Use Binary Search to find the largest element i.e. the pivot. The element in the array after the pivot i.e. nums[pivot + 1] is your answer. If mums[nums.Length] > nums[0], then you know it can be considered as a sorted array without rotation. In that case, simply return nums[0].

You are correct! The pivot in the array (the point where it was rotated) can be found using a binary search. The pivot is the largest element in the array, and the smallest element is located directly after it (or at the start of the array if there was no rotation). Here is a Python implementation of your approach:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def findMin(nums):
    if len(nums) == 1:
        return nums[0]
    
    left = 0
    right = len(nums) - 1
    
    if nums[right] > nums[0]:  # If no rotation
        return nums[0]
    
    while right >= left:
        mid = left + (right - left) // 2
        
        if nums[mid] > nums[mid + 1]:  # If mid element is the pivot
            return nums[mid + 1]
        
        if nums[mid - 1] > nums[mid]:  # If (mid - 1)th element is the pivot
            return nums[mid]
        
        if nums[mid] > nums[0]:  # If mid element is greater than 0th element, move left to mid
            left = mid + 1
        else:  # If mid element is less than 0th element, move right to mid
            right = mid - 1

This code works by first checking if the array is rotated. If it isn’t, we immediately return the first element. Then, it enters a binary search. If the midpoint is greater than the next point, it means we’ve found the pivot and thus return the next number. If the previous number is greater than the midpoint, we’ve just passed the pivot and thus return the midpoint. Otherwise, we move our search boundaries based on whether the midpoint is greater or less than the first element in the array.

We only need to compare mid with right one in the binary search. This will automatically handle the edge case of already sorted array. I was so confused as to why the mid was only being compared to the right.

Yes, you’re absolutely correct! Comparing the mid element with the right element of the array helps to determine which half of the array to search next. This approach not only simplifies the code but also effectively handles the edge case where the array is already sorted.

The reason behind comparing the mid element with the right element is that the smallest element in a sorted and rotated array is always in the “unsorted” part of the array. The array can be divided into two portions based on the mid element:

  1. If nums[mid] > nums[right], it means the left half from mid is sorted, and the smallest number must be in the right half, which is unsorted.

  2. If nums[mid] <= nums[right], it means the right half from mid is sorted, and the smallest number must be in the left half, which is unsorted.

So, here is how we can code it:

1
2
3
4
5
6
7
8
9
def findMin(nums):
    left, right = 0, len(nums) - 1
    while left < right:
        mid = left + (right - left) // 2
        if nums[mid] > nums[right]:  # If mid element is greater than right, move left to mid + 1
            left = mid + 1
        else:  # If mid element is less or equal to right, move right to mid
            right = mid
    return nums[left]  # when left == right, loop ends, and left (or right) is the smallest number

This approach also provides a O(log n) time complexity, which is efficient for large inputs. It’s a smart way to handle this problem, leveraging the sorted and rotated properties of the array.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
    int findMin(vector<int>& nums) 
    {
          
        int l=0;
        int r=nums.size()-1;
        
        while(l<r)
        {
            int mid=l+(r-l)/2;
            
            if(nums[mid]>nums[r])
                l=mid+1;
            else 
                r=mid;
        }   
        return nums[l];
    }
};

this fails in case of monotonic array

Actually, the provided solution also works correctly for monotonically increasing arrays (a type of sorted array with no rotations). Let’s look at an example:

Suppose we have the array nums = [1,2,3,4,5].

At the beginning, l = 0, r = 4, so mid = 2. nums[mid] = 3 and nums[r] = 5. Since nums[mid] is not greater than nums[r], we move r to mid, which is 2.

In the next iteration, l = 0, r = 2, so mid = 1. nums[mid] = 2 and nums[r] = 3. Again, nums[mid] is not greater than nums[r], so we move r to mid, which is 1.

In the final iteration, l = 0, r = 1, so mid = 0. nums[mid] = 1 and nums[r] = 2. nums[mid] is not greater than nums[r], so we move r to mid, which is 0.

Now l and r both point to 0, and the loop ends. We return nums[l], which is 1 — the minimum element in the array.

This confirms that the code does work for monotonically increasing arrays. It always finds the pivot point where the array is “rotated” (which, in the case of a monotonically increasing array, is the first element).

This question is all about pivot element. simply find pivot using binary search, and return min(nums[0], nums[pivot]).

Finding the pivot using binary search is indeed one way to solve this problem, but it seems there is a small misunderstanding in your proposed solution.

In this problem, the pivot is the point of rotation, which is essentially the maximum element in the array. The minimum element is located just after the pivot (or the first element if the array is not rotated).

So, after finding the pivot, you should return nums[pivot + 1] (assuming pivot + 1 is within the array’s boundaries) instead of min(nums[0], nums[pivot]). If pivot + 1 exceeds the array’s boundaries, it means the array is not rotated, and you should return nums[0].

Let’s write this out as Python code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Solution:
    def findMin(self, nums: List[int]) -> int:
        left, right = 0, len(nums) - 1
        while left < right:
            mid = (left + right) // 2
            if nums[mid] > nums[right]:  # pivot is in the right half
                left = mid + 1
            else:  # pivot is in the left half
                right = mid
        return nums[left]

This solution keeps shrinking the search space by half each iteration until it finds the minimum element, which is the essence of binary search, thus ensuring a time complexity of O(log n).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Solution {
	public int findMin(int[] nums) {
		int st = 0, end = nums.length - 1;
		int ans = -1;
		while(st <= end){
			int mid = st + (end - st)/2;
			if(nums[end] < nums[mid]){
				st = mid + 1;
			}else if(nums[end] >= nums[mid]){
				ans = mid;
				end = mid - 1;
			}
		}
		return nums[ans];
	}
}

why I am getting wrong answer? for the testcase - {3, 1, 2}, it is giving the output as 3 instead of 1.

The issue in your code is the line ans = mid;. In your binary search, you are assigning mid to ans when nums[mid] is less than or equal to nums[end]. This means you’re storing the index of the smallest number found so far. However, in the case of a rotation, there may be smaller numbers after this index in the array.

When you reach the smallest element, end is updated to mid - 1 and then st becomes greater than end in the next iteration. This breaks the loop and you return nums[ans] as the minimum element. But ans is not updated in this final iteration and holds the value from the previous iteration, hence the wrong answer.

A better approach would be to directly return nums[st] after the loop, because st would always point to the smallest element when the loop ends. Here’s the corrected code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Solution {
    public int findMin(int[] nums) {
        int st = 0, end = nums.length-1;
        while(st < end) {
            int mid = st + (end - st)/2;
            if(nums[end] < nums[mid]) {
                st = mid + 1;
            } else {
                end = mid;
            }
        }
        return nums[st];
    }
}

This solution is based on the insight that the smallest element is always to the right of the pivot. So, we keep moving the start to mid + 1 if the mid element is greater than the end element. If it’s not, that means we’re already in the sorted part of the array and we update the end to mid. We keep doing this until start and end meet, which would be pointing to the smallest element.

I think the main idea here is to look in the unsorted half of the array. If it was shifted more than len(nums)//2 - the minimum will be on the right half. If it was shifted less/equal than len(nums)//2 - the minimum will be on the left half.

the // signify floor.

Yes, you’re right. Your observation is key to understanding the approach for this problem. Binary search is a perfect tool in this case since it allows us to effectively “divide and conquer” our search space.

Here’s a more detailed breakdown of the concept:

  1. When the array is rotated, the pivot point (the point of rotation) is the only point where the next number is smaller than the current one. Therefore, the smallest element in a rotated array is always found at the pivot.

  2. If the array is not rotated (i.e., rotated an entire length or not rotated at all), the smallest element will be the first element.

  3. In the binary search process, we are always comparing the mid element with the right-end element. This comparison will tell us which half is sorted:

    • If nums[mid] > nums[right], then the array is rotated and the smallest number is in the right half. This is because the mid number is greater than the right end, which means the right half isn’t sorted.

    • If nums[mid] <= nums[right], then the smallest number must be in the left half (including mid). This is because the mid number is less than or equal to the right end, which means the right half is sorted.

Following this rule, we can move the left or right pointers accordingly in the binary search until they meet each other, at which point we will have found the smallest element.

This binary search technique ensures that we find the minimum element in O(log n) time, satisfying the constraints of the problem.

I found a solution guys.

return min(nums)

While it’s true that the Python built-in min() function can find the minimum value in the list, this approach doesn’t satisfy the time complexity constraint provided in the problem statement.

The min() function essentially scans the entire list to find the smallest element, which has a time complexity of O(n), where n is the number of elements in the list. This is too slow for large lists and does not utilize the properties of the list (sorted and rotated).

The problem statement asks for a solution with a time complexity of O(log n), implying that a more efficient algorithm like binary search should be used. Binary search works by repeatedly dividing the search space in half, which is much faster than checking every element individually for large lists.

Here’s a Python code that uses a binary search algorithm to solve this problem:

1
2
3
4
5
6
7
8
9
def findMin(nums):
    left, right = 0, len(nums) - 1
    while left < right:
        mid = (left + right) // 2
        if nums[mid] > nums[right]:
            left = mid + 1
        else:
            right = mid
    return nums[left]

This solution takes advantage of the fact that the array is sorted (aside from the rotation) to quickly find the minimum element.

just compare the mid values of array with the low and the high and at any point of time either the left half or the right half is always sorted so this comparison can easily be put into a if else condition. If left half is sorted take the nums[low] value as it can be potential answer and move to the right part to get the minimum in that part. At last compare both the minimums and return the lesser number.

The approach you suggested works for the case where the array is rotated. However, if the array is not rotated (i.e., sorted in ascending order), comparing mid with low and high doesn’t help to determine the next search space.

Instead, we should compare the mid value with the right (or last) value. This comparison gives us a clear rule to determine the next search space: If nums[mid] > nums[right], then the minimum must be in the right half (i.e., nums[mid+1 : right+1]), else the minimum is in the left half (i.e., nums[left : mid+1]).

Let’s translate this into Python code:

1
2
3
4
5
6
7
8
9
def findMin(nums):
    low, high = 0, len(nums) - 1
    while low < high:
        mid = low + (high - low) // 2
        if nums[mid] > nums[high]:
            low = mid + 1
        else:
            high = mid
    return nums[low]

This code implements binary search, and it continuously narrows down the search interval from [low, high] to [mid+1, high] or [low, mid], depending on the comparison between nums[mid] and nums[high]. The loop continues until low == high, at which point we’ve found the minimum element.

This algorithm is O(log N) as it halves the search space at each step.

Most naive solution is to sort and return nums[0].

While that approach would certainly work and return the correct result, it does not meet the performance requirement stated in the problem, which demands an algorithm that runs in O(log n) time. Sorting an array typically requires O(n log n) time, which is more costly than the required O(log n).

The problem statement also indicates that the array is already sorted, but rotated. So, sorting again isn’t taking advantage of the existing sorted nature of the array.

Here, binary search can be used to take advantage of the sorted nature of the array and find the minimum element in O(log n) time, which meets the problem’s constraints. The goal is to identify the point of rotation (i.e., the minimum element), which can be done in a divide-and-conquer manner, as shown in previous discussions.

Is there anybody who gets confuse while setting condition for binary search like whethere it should be (l < r) or (l <= r) or like whether it should be l = mid - 1 or l = mid or r = mid + 1 or r = mid. This stuff confuses me a lot. Someone please share your thoughts to clear.

Yes, setting the right conditions in a binary search can be tricky and it’s one of the most common sources of confusion when implementing it. Here’s a simple guideline to follow:

  1. l < r vs l <= r: The condition l < r is generally used when you’re doing a binary search where the search space reduces on each step, meaning that at some point l and r will be the same. When they are the same, you know you’ve found the element or it does not exist. l <= r is used when you’re doing a binary search where you need to consider the middle element as well.

  2. l = mid + 1 vs l = mid and r = mid - 1 vs r = mid: These decisions are often problem-specific and depend on whether you are looking for the first occurrence, last occurrence, or any occurrence. Usually, when the middle element is not the target, you move one step to the left or right to skip the current middle element.

Remember, the best way to get comfortable with these decisions is to practice problems involving binary search and try to understand the reasoning behind the conditions.

In this problem, we use l < r because we are reducing the search space on each step, and l = mid + 1 or r = mid because we have already checked the mid element, and we are sure that it’s not the answer so we need to move one step further.

Visualizing these steps can be very helpful. Let’s consider an array: [4, 5, 6, 7, 0, 1, 2].

Step 1. l < r vs l <= r:

A binary search algorithm repeatedly divides the search space (the portion of the array we are interested in) in half. l and r represent the bounds of this search space. Initially, l is at the start of the array, and r is at the end.

When we say l < r, it means the search continues as long as the left boundary is strictly less than the right boundary, indicating that there is more than one element left in the search space.

Here is a quick visualization:

Array:  4  5  6  7  0  1  2
Index:  0  1  2  3  4  5  6
         ^           ^
         |           |
         l           r

After a few iterations:

Array:  4  5  6  7  0  1  2
Index:  0  1  2  3  4  5  6
                  ^  ^
                  |  |
                  l  r

When l and r point to the same element, we stop, because we’ve narrowed down the search space to a single element.

Step 2. l = mid + 1 vs l = mid and r = mid - 1 vs r = mid:

Let’s say we’re at a point where l is at index 3 (value 7), r is at index 6 (value 2), and mid is at index 4 (value 0). Here we find that nums[mid] < nums[r]. So, the minimum must be in the left part of the array. We then set r to mid.

Why not mid - 1? Because the mid element is a potential answer (it could be the minimum), so we include it in our next search space.

Array:  4  5  6  7  0  1  2
Index:  0  1  2  3  4  5  6
                  ^  ^  ^
                  |  |  |
                  l mid r
                  
Next iteration:

Array:  4  5  6  7  0  1  2
Index:  0  1  2  3  4  5  6
                  ^  ^
                  |  |
                  l/r(mid)    

We can see here that including mid in our search space allows us to find the minimum element in the array.

i tried using the condition nums[mid]>=nums[0] in the if statement but it works for only rotating the array by two but nums[mid]>=nums[e] is the correct one

You’re on the right track! Let’s break down why nums[mid] >= nums[e] is the correct condition in this scenario.

In a sorted and rotated array where all numbers are unique, the pivot point will always be the maximum number (let’s call it max). The elements to the left of the max are in ascending order and all larger than the elements to the right of max.

This leads us to the following conclusions:

  1. If nums[mid] >= nums[0], this tells us that mid is on the left side of max. This condition is not sufficient to determine where the minimum value is. As you mentioned, it only works for some cases.

  2. On the other hand, if nums[mid] >= nums[e], this means that mid is on the left side of max and all the elements to its right are smaller than nums[mid]. Therefore, the minimum value must lie in the right half. Conversely, if nums[mid] < nums[e], the mid is on the right side of max, so the minimum must be on the left. This condition correctly guides the binary search towards the minimum value.

That’s why you should use nums[mid] >= nums[e] to determine which side of the array to search. This makes sure that the search space is always decreased by half and that the minimum element is within the search space.

How does the below solution work? Its a medium problem under binary search, am I missing anything?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public int findMin(int[] nums) 
{ 
	int min = Integer.MAX_VALUE; 
	for(int i = 0;i < nums.length; i++){ 
		if(nums[i] < min){ 
			min = nums[i]; 
		} 
	} 
	return min; 
}

The solution you posted does solve the problem, but it doesn’t leverage the fact that the array is sorted and then rotated. The code iterates over each element in the array, compares it to the current minimum, and updates the minimum if necessary. This is a brute-force solution and its time complexity is O(n), where n is the size of the input array.

However, the problem statement asks for an O(log n) solution, indicating that a more efficient solution is possible. This is a hint that you can use binary search, a common algorithm for efficiently searching a sorted list.

In the context of this problem, the key insight is to use binary search to find the “pivot”, the point where the array was rotated. This is the point of minimum value. The property of the pivot is that it’s the only point where the previous number is larger. By comparing the middle element with the last one, we can determine which half of the array the pivot lies in and continue our search there.

Here’s an example of how to implement this in Python:

1
2
3
4
5
6
7
8
9
def findMin(nums):
    low, high = 0, len(nums) - 1
    while low < high:
        mid = (low + high) // 2
        if nums[mid] > nums[high]:  # pivot must be in the right half
            low = mid + 1
        else:  # pivot must be in the left half
            high = mid
    return nums[low]  # when low == high, we've found the minimum

This binary search solution works in O(log n) time, meeting the problem’s constraints. It also uses O(1) space.

Let’s say array before sorting is: a0, a1, ……., a(n-1)

Array after rotation: a(n-k), a(n-k+1), ……, a(n-1), a0, a1, …., a(n-k+1)

No many how many times array is rotated, a0 will always remain the minimum element. So, idea here is to divide the array into two halves, if the first half is in increasing order, then it means that a0 lies in the second half, otherwise in the first half.

Note that question can be re-written as finding the minimum index.

Yes, you’re correct. The key insight to this problem is that no matter how many times the sorted array is rotated, the smallest element is the pivot point (or rotation point). This element is unique as it’s the only point where the previous element is larger.

The array is divided into two portions, one that is sorted in ascending order and one that is sorted in descending order. We can use binary search to find this pivot point.

If the mid element is greater than the end element, it means the pivot (minimum element) lies in the right half of the array. So, we move the low pointer to mid + 1.

If the mid element is less than or equal to the end element, it means the pivot (minimum element) is in the left half of the array, so we move the high pointer to mid.

The binary search continues until low equals high, which is when we have found the minimum element.

Here’s a Python example to illustrate the process:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def findMin(nums):
    low, high = 0, len(nums) - 1
    
    while low < high:
        mid = (low + high) // 2
        if nums[mid] > nums[high]:    # Pivot point is in right half
            low = mid + 1
        else:                         # Pivot point is in left half
            high = mid

    return nums[low]                  # When low == high, we've found the minimum

class Solution { public: int findMin(vector& nums) { int s=0; int e = nums.size()-1; int m;

	if(nums[s] <= nums[e]){
		return nums[0];
	}
	while(s < e)
	{
		int m = s + (e-s)/2;
		if(nums[m] < nums[m-1]) {
			return nums[m];
		}
		else if(nums[m] > nums[m+1]){
			return nums[m+1];
		}
		else if (nums[s] < =nums[m]){
			s = m + 1;
		}
		else if(nums[e]>=nums[m]){
			e = m-1;
		}

	}  
	return nums[s];
}

};

// why i am getting run time error //all test case passes but shows run time error

The error is likely due to accessing an out-of-bounds index in the array nums at these lines:

1
2
3
4
5
6
if(nums[m]<nums[m-1]) {
    return nums[m];
}
else if(nums[m]>nums[m+1]){
    return nums[m+1];
}

At the beginning of the loop, when m is at 0 (which is the starting point), m-1 will be -1. Similarly, at the end of the loop when m is at the last index, m+1 will be out of bounds.

To fix these errors, you should modify your code to only check nums[m] against nums[m-1] and nums[m+1] if m-1 and m+1 are valid indices, respectively. Here’s how you can do it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
public:
    int findMin(vector<int>& nums) {
        int s=0; 
        int e=nums.size()-1;
        
        // If array is already sorted, return the first element
        if(nums[s] <= nums[e]){
            return nums[0];
        }
        
        while(s < e)
        {
            int m = s + (e - s) / 2;
            
            // Check if nums[m] is smaller than its predecessor
            if(m > 0 && nums[m] < nums[m-1]) {
                return nums[m];
            }
            // Check if nums[m] is greater than its successor
            else if(m < nums.size() - 1 && nums[m] > nums[m+1]){
                return nums[m+1];
            }
            else if (nums[s] <= nums[m]){
                s = m + 1;
            }
            else if(nums[e] >= nums[m]){
                e = m - 1;
            }
        }
        
        return nums[s];
    }
};

This version of the code avoids the runtime errors by ensuring that the indices used to access the elements in nums are always within bounds.

Getting runtime error:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
	int findMin(vector& nums) {
		int n = nums.size();
		int ans = 0;
		if(nums[0] < nums[n-1]){
			return nums[0];
		}
		int mid = n/2;
		if(nums[mid] < nums[mid + 1] && nums[mid] < nums[mid - 1]){
			return nums[mid];
		}
		if(nums[mid] > nums[0]){
			vector slice(nums.begin() + mid, nums.end());
			ans = findMin(slice);
		}
		else{
			vector slice(nums.begin(), nums.end() - mid);
			ans = findMin(slice);
		}
		return ans;
	}
};

The issue lies in the condition where you’re checking the middle value of the array. It fails when you’re considering an array with only 1 or 2 elements.

1
2
3
if(nums[mid] < nums[mid+1] && nums[mid] < nums[mid-1]){
    return nums[mid];
}

In the above condition, you’re accessing mid+1 and mid-1, which may not be valid indices if the size of nums is 1 or 2.

Instead, you should only perform this check when the size of nums is larger than 2:

1
2
3
if(n > 2 && nums[mid] < nums[mid + 1] && nums[mid] < nums[mid - 1]) {
    return nums[mid];
}

In addition, the way you’re slicing the array seems incorrect:

1
vector slice(nums.begin(), nums.end()-mid);

In the above line, you’re trying to construct slice from the beginning of nums to mid elements before the end of nums. This would give you more than half of the array when you’re supposed to get the first half. You likely intended to do:

1
vector slice(nums.begin(), nums.begin() + mid);

The correct code would be:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
    int findMin(vector<int>& nums) {
        int n = nums.size();
        int ans = 0;
        if(nums[0] < nums[n-1]) {
            return nums[0];
        }
        int mid = n / 2;
        if(n > 2 && nums[mid] < nums[mid + 1] && nums[mid] < nums[mid - 1]) {
            return nums[mid];
        }
        if(nums[mid] > nums[0]) {
            vector<int> slice(nums.begin() + mid, nums.end());
            ans = findMin(slice);
        }
        else {
            vector<int> slice(nums.begin(), nums.begin() + mid);
            ans = findMin(slice);
        }
        return ans;
    }
};

This version of the code avoids the runtime error by ensuring that the indices used to access elements in nums are always within bounds and correctly slicing the array. Note that this code is not the most efficient solution to the problem and it’s for demonstration purpose. The most efficient solution would be the binary search approach mentioned earlier.

I just feel that in the end , nums[middle] should be compared with nums[start] and not nums[0]. Also , in case nums[middle] < nums[start] we should search between start and middle not middle-1

You’re correct in both of your observations.

  1. nums[middle] should be compared with nums[start]. The reason is that start keeps track of the “beginning” of the portion of the array we’re considering at each step, while 0 always points to the very first element of the original, unaltered array. As we continue to narrow our search, comparing nums[middle] with nums[0] might lead to incorrect decisions because nums[0] might not even be part of the portion of the array we’re considering.

  2. If nums[middle] < nums[start], then the minimum element must be somewhere to the left of middle, including middle itself. Therefore, we should search between start and middle, not middle - 1.

In light of these insights, here’s a corrected version of the Python code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Solution:
    def findMin(self, nums: List[int]) -> int:
        start, end = 0, len(nums) - 1

        while start < end:
            middle = (start + end) // 2

            if nums[middle] > nums[end]:
                start = middle + 1
            else:
                end = middle

        return nums[start]

In this code, we perform a binary search, iteratively narrowing our search to the half of the array that must contain the minimum element. We decide which half to choose based on comparing nums[middle] and nums[end]. We continue until start and end meet, at which point they both point to the minimum element.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution:
    def findMin(self, nums: List[int]) -> int:
        l=len(nums)
        start=0
        end=l-1
        if(l==1):
            return nums[0]
            if(l==2):
                if(nums[0] < nums[1]):
                    return nums[0]
                else:
                    return nums[1]
                    while(start <= end):
                        mid = start + (end - start)//2
                        prev = (mid + l - 1) % l
                        nex = (mid + 1) % l
                        if(nums[mid] < nums[prev] and nums[mid] < nums[nex]):
                            return nums[mid]
                        elif(nums[mid] < nums[end]):
                            end = mid - 1
                        elif(nums[mid] > nums[start]):
                            start = mid + 1

can anyone tell what is wrong with this code ?

Your approach seems almost correct, but there’s a little confusion in the else if conditions. In a sorted and rotated array, the pivot (the point of rotation, i.e., the minimum element) is the only point where the next number is less than the current.

The elements to the right of the pivot are smaller than the elements to the left of the pivot. Therefore, we have two sorted subarrays and we need to decide which part to discard depending upon the mid element. Here’s the correct code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Solution:
    def findMin(self, nums: List[int]) -> int:
        if len(nums) == 1:
            return nums[0]
        left = 0
        right = len(nums) - 1
        if nums[right] > nums[0]:  # no rotation
            return nums[0]
        while right >= left:
            mid = left + (right - left) // 2
            if nums[mid] > nums[mid + 1]:  # point of change is the smallest element
                return nums[mid + 1]
            if nums[mid - 1] > nums[mid]:  # point of change is the smallest element
                return nums[mid]
            if nums[mid] > nums[0]:  # left side is sorted so the pivot is on right side
                left = mid + 1
            else:  # right side is sorted so the pivot is on the left side
                right = mid - 1

This solution works in O(log n) time as it discards one of the halves in each step, and it uses O(1) space.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
	int findMin(vector& nums) {
		int res;
		int l = 0;
		int r = (nums.size())-1;
		while(l<=r){
			if(nums[l] < nums[r]){
				res = min(res, nums[l]);
			}
			int m = l + (r - l)/2;
			res = min(res, nums[l]);
			if(nums[m]>=nums[l]){
				l = m + 1;
			}
			else{
				r = m - 1;
			}
		}
		return res;
	}
};

1 test case is showing error [3, 1, 2]

Your approach is almost correct, but it seems you made a mistake in the line:

1
res = min(res, nums[l]);

This should be:

1
res = min(res, nums[m]);

The correct code will look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Solution {
public:
    int findMin(vector<int>& nums) {
        int res = nums[0];
        int l = 0;
        int r = nums.size() - 1;
        while (l <= r) {
            int m = l + (r - l) / 2;
            res = min(res, nums[m]);
            if (nums[m] >= nums[l]) {
                l = m + 1;
            } else {
                r = m - 1;
            }
        }
        return res;
    }
};

The reason for this is that you are trying to find the minimum element, and you should compare and update the current minimum value with the middle element nums[m], not the left boundary nums[l].

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
	public int findMin(int[] nums) {
		int size=nums.length;
		int s=0,e=size-1;
		while(s<=e){

			int m=s+(e-s)/2;
			if(nums[m]<=nums[m-1] && nums[m]<=nums[m+1] && size>2){
				return nums[m];
			}
			else{
				System.out.println("inside else"+nums[s]+","+nums[m]+","+nums[e]);
				if(nums[m+1]<nums[m-1]){
					s=m+1;
				}else if(nums[m+1]>nums[m-1]){
					e=m-1;
				}
			}
		}
		return nums[0];
	}
}

GETTING BELOW ERROR: java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 4 at line 8, Solution.findMin at line 54, DriverSolution.helper at line 84, Driver.main

Your issue arises from accessing array indices without verifying if they are valid. In particular, these two lines are problematic:

1
if(nums[m]<=nums[m-1] && nums[m]<=nums[m+1] && size>2){

and

1
if(nums[m+1] < nums[m-1]){

In both lines, you’re accessing m-1 and m+1 without checking whether m-1 is >=0 and m+1 is < size. When m is 0 or size-1, these conditions will lead to the ArrayIndexOutOfBoundsException.

Here is the corrected version of your code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class Solution {
    public int findMin(int[] nums) {
        int size = nums.length;
        int s = 0, e = size-1;
        while(s < e){
            int m = s + (e-s)/2;
            if(nums[m] > nums[e]){
                s = m + 1;
            }else {
                e = m;
            }
        }
        return nums[s];
    }
}

This is a simpler and safer implementation using binary search that avoids problematic index access. The code always compares the middle element with the end element and moves the search range accordingly, which guarantees that the search range will shrink and eventually reach the minimum element.

why do we use mid=start+(end-start)/2;? and not mid=(start+end)/2; below is the code in java tht is accepted but im unable to understand the above stmts. Also if we are checking if nums.length==1 return nums[0]

then why do we use while(start<=end) can anybody clear this doubt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
	public int findMin(int[] nums) {
		int n=nums.length;
		if(n==1) return nums[0];
		int start = 0, end=n-1;
		if(nums[0] < nums[end]) return nums[0];

		while(start <= end)
		{
			int mid = start + (end - start) / 2;
			if(nums[mid + 1] < nums[mid]) return nums[mid + 1];
			if(nums[mid - 1] > nums[mid]) return nums[mid];

			if(nums[mid]>nums[0])
			{
				start = mid+1;
			}
			else end = mid-1;
		}
		return -1;
	}
}

In this case, mid = start + (end - start) / 2 and mid = (start + end) / 2 might seem to do the same thing - finding the middle index of a range in an array. However, there is a subtle but important difference that comes into play when working with large numbers.

In some languages, such as Java, C++, and Python, the sum start + end might exceed the maximum positive int value (2^31 - 1 for Java and C++). This can result in overflow, causing incorrect calculations or errors. To avoid this issue, we use the formula start + (end - start) / 2 to find the mid-point without risking overflow.

Regarding your second question, the while (start <= end) loop is a typical construct when using binary search, which is the technique employed in this problem. Although you return nums[0] when n == 1, there are other cases where the array length is larger than 1 and the smallest number is at index 0 (like a sorted array without any rotation). The condition nums[0] < nums[end] checks if the array is already sorted and simply returns nums[0] if that is the case. If the array has been rotated, the binary search is executed to find the minimum.

Hey Guys,

I have used the below approach and stuck. Can someone point, where am I going wrong?

I feel confused!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Solution:
    def findMin(self, nums: List[int]) -> int:
        low, high = 0, len(nums) - 1

        while low < high:
            mid = low + (high - low) // 2

            if nums[low] < nums[mid]:
                # the left part of the array is sorted
                low = mid
            else:
                if nums[mid] < nums[high]:
                    # right part of the array is sorted but left part of the array is not sorted
                    high = mid
                # else:
                    # left and right part of the array is not sorted. 
                    # I don't understand what to do here! if both parts of the array is not sorted, how will I reduce the search space?
                    
        return nums[low]

Your approach is good and is aligned with the binary search solution for this problem, but you seem to have some misconceptions.

In a rotated sorted array, one half of the array is always sorted. If the array isn’t sorted, that means it has been rotated and the minimum element is somewhere in the unsorted part.

Let’s correct your approach:

  1. Find the mid element.
  2. If the mid element is greater than the high element, then the minimum element lies in the right half. So, set low = mid + 1.
  3. Else, the minimum element lies in the left half, so set high = mid.

The termination condition for the while loop should be low < high, not low <= high. When low == high, that’s when you have found the smallest element.

Here’s the corrected code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Solution:
    def findMin(self, nums: List[int]) -> int:
        low, high = 0, len(nums) - 1

        while low < high:
            mid = low + (high - low) // 2

            if nums[mid] > nums[high]:
                low = mid + 1
            else:
                high = mid

        return nums[low]

This code works because in a rotated sorted array, if you split it into two halves, then one must be sorted and one must be a rotated sorted array. The minimum element is either the first element of the sorted half (if there are no rotations), or it lies within the rotated sorted half. By comparing the middle element and the last element, you can tell which half is sorted and which half is rotated. If the middle element is greater than the last element, then the left half is sorted and the right half is a rotated sorted array. If the middle element is less than the last element, then the right half is sorted and the left half is a rotated sorted array. In either case, you can adjust your search boundary (low or high) accordingly. You repeat this process until your search boundary contains only one element, which is the smallest element in the array.

I am really curious how you came up with these conditions in your answer.

if nums[mid]>nums[right]: left = mid+1 else: right = mid In one of your previous replies to a comment, you have written ‘I think it’s best to think about the binary search conditions (for any problem that uses it) in terms of which parts of the array get disqualified for the next iteration.’

I tried various combinations of conditions, but none seem to work with all test cases. I want to know what made you choose the condition you chose in your solution.

For example, why didn’t you choose this

if nums[left] > nums[mid]:
    right = mid - 1
else:
    left = mid
or this

if nums[left]>=nums[mid]:
    right = mid
else:
    left = mid + 1

I know these don’t pass all the test cases. But like I said, I want to know what gave you the idea for your solution.

Understanding the decision to choose the conditions in binary search for this problem requires understanding the properties of the problem itself. We are dealing with a sorted array that has been rotated at some pivot unknown to you beforehand.

Given the array nums that is sorted in ascending order, then rotated at some pivot unknown to you beforehand (i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]), find the minimum value from the array.

In a sorted and rotated array, there is one property you can note: any pivot element (the smallest element) divides the array into two sorted arrays. Thus, you can use binary search with a slight modification.

If you choose the mid element, and if the array is rotated, then one of the two conditions always holds:

  1. nums[mid] > nums[high]: This condition means that the smallest value is on the right side. This is because if mid element was greater than the high element, we can be sure that mid element is greater than all the elements to the right from it. Because the elements at the right are in ascending order now, smallest element will be to the right.

  2. nums[mid] <= nums[high]: This condition means that the smallest value is on the left side including itself. As the middle element is smaller, the minimum element can’t be on the right (it could be the mid element though).

This forms the basis for the conditions in the binary search.

As for why other conditions like nums[left] > nums[mid] or nums[left] >= nums[mid] are not used: these conditions check with the start of the array, but since the array is rotated, the start of the array isn’t guaranteed to be a good reference point. Comparing with the end of the array is safer, because no matter how you rotate the array, the pivot element (smallest element) is always just to the right of the largest element (which is what high points to until high crosses over to point to the smallest element).

This is why the conditions in this solution are chosen. They are based on the properties of sorted, rotated arrays and take advantage of those properties in a binary search algorithm.

Here is what i think the reason is Case 1

if nums[left]>nums[mid]:
   right = mid-1
else:
   left = mid

Here the if loop identifies unsorted portion. So smallest should be within this subarray. There is a possibility that element at mid index is smallest. So we cannot move left=mid+1 or right=mid-1. Also we cannot move left=mid as it is possible that mid-1 is smallest. So only feasible option is right=mid. Now if we move right=mid for if loop , then in the else loop we have to move left=mid+1 to shrink the search space with this binary search template(while(left < right)). But the logic wont work for completely sorted array. If we are in sorted portion we have to move left to mid as the low can be mid. So for these two different test cases, the movement of left is conflicting.

Yes, your understanding is correct!

When we are looking for the minimum value in a sorted and rotated array, the key insight is that one half of the array is always sorted. If the current mid element is less than the right end, we know that the right half of the array is sorted, and thus, the minimum value must be to the left, including possibly at mid. That’s why we set right = mid in this case.

If the mid element is greater than the right end, we know that the left half of the array is sorted, and thus, the minimum value must be to the right. So, we set left = mid + 1 in this case.

In your example of nums[left] > nums[mid], you’re trying to find the unsorted portion of the array. This is a valid approach, but in a sorted and rotated array, the pivot (minimum element) can be at the mid index itself. So, if we set right = mid - 1, we could potentially skip over the minimum value, which is why we set right = mid instead.

In binary search, the goal is to progressively shrink the search space while ensuring that the desired value is always within the search space. So, the conditions chosen must reflect this goal and be based on the specific properties of the problem, which in this case, is a sorted and rotated array.

high = mid - 1 actually makes no sense here.

Let’s take the else block for example. In the if of the first else block, we know it’s the case that nums[low] < nums[mid] < nums[high]. Great. This means that the entire array is sorted, and we can just return nums[low]. However, if we hit the else condition, we know nums[low] < nums[mid] > nums[high]. We then know that we must explore between mid and high, because anytime this case is true, the min will be found in the right half. Here’s an example: [4, 5, 6, 0, 1]. We explore this new right hand section with low = mid + 1. Doing high = mid - 1 is the correct way to search the left half, but it is not where we will find the minimum because we would then instead be exploring [4,5] instead of [0,1].

Yes, you’re correct in your understanding.

Binary search works by continuously dividing the search space in half. If we know that a sorted sequence has been rotated at some pivot, then either the left half or right half of the sequence must remain sorted. In the case of a sorted and rotated sequence where we’re trying to find the minimum, we’re essentially trying to find the pivot point.

For nums[mid] > nums[high], this condition is telling us that the mid point is in the sorted portion of the array and the pivot point (smallest number) is somewhere to the right, so we move our search to the right half by setting low = mid + 1.

For nums[mid] <= nums[high], this condition is saying that the mid point is in the unsorted half and thus the pivot point must be at or to the left of mid. So we continue our search in the left half, but we can’t exclude mid from our search so we set high = mid.

The goal is to narrow down the search to the smallest element. So you’re correct, high = mid - 1 would not be a valid operation in this scenario as it could potentially exclude the smallest element from the search space.

What I am not getting are these 2 lines:

if nums[low] > nums[mid]:
            # If we are here, it means that the range low to mid is unsorted, so we will discard the right part
            high = mid
and this one

else:
       # If the right part is not sorted, so the min will lie in that range (the unsorted range)
       low = mid + 1

Why not do high = mid - 1 instead just how we do low = mid + 1.

I can see the test case failing but yet to come up with an intuition on why that is the case. Will look further, but if you have insight, do let us know.

The main idea is that the minimum value in a sorted and rotated array is the pivot - the point of rotation. We are trying to find that pivot point.

The conditions in the if-else block are to help us determine in which half of the current segment the pivot might lie, and update our search bounds accordingly.

  1. If nums[low] > nums[mid]: This means the left half of our segment isn’t sorted, and since the pivot point is the only place where the order breaks, the pivot must be in this half. So, we continue our search only in this half. high = mid because the pivot could be at position mid, we can’t exclude it.

  2. Otherwise (else), the left half of our segment must be sorted. Since it’s sorted and we know nums[low] (the smallest number in this sorted half) is not the minimum in the whole array (we have checked nums[low] > nums[mid] before), the pivot must be somewhere in the right half. Thus, we continue our search in the right half. low = mid + 1 because we know that the number at position mid is not the pivot (it’s not the minimum in the whole array).

If we did high = mid - 1, in the first case, we could potentially be excluding the pivot point from our search space, which would lead to incorrect results. Same reason goes for not doing low = mid in the second case, where we could exclude the pivot again. That’s why we need to be careful about how we update low and high in binary search kind of algorithms.

The crux of the problem is that the lowest index will lie in the unsorted range or nums[0] if whole array is sorted I will modify your first snippet to make it work. I will use your this statement in every step

In one of your previous replies to a comment, you have written ‘I think it’s best to think about the binary search conditions (for any problem that uses it) in terms of which parts of the array get disqualified for the next iteration.’

        while low < high:
            mid = low + (high-low)//2
            if nums[low] > nums[mid]:
            # If we are here, it means that the range low to mid is unsorted, so we will discard the right part
                high = mid
            else:
            # If we are here, it means the range low to mid is sorted, so the min element will be nums[low] or in the range mid to high
                if nums[mid] < nums[high]:
                    # If we are here, it means the right part is also sorted. So now the left part is also sorted and right part is also sorted, So nums[low] will be the lowest
                    return nums[low]
                else:
                    # If the right part is not sorted, so the min will lie in that range (the unsorted range)
                    low = mid + 1
        return nums[low]

You’re correct. In the context of this problem, the main idea is that the minimum element (pivot point) is always found in the unsorted half of the array. If both halves appear to be sorted, that means the array hasn’t been rotated and the minimum element is simply the first one.

Your modification of the code snippet correctly captures this concept:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
while low < high:
    mid = low + (high-low)//2
    if nums[low] > nums[mid]:
        # If we are here, it means that the range low to mid is unsorted, so we will discard the right part
        high = mid
    else:
        # If we are here, it means the range low to mid is sorted, so the min element will be nums[low] or in the range mid to high
        if nums[mid] < nums[high]:
            # If we are here, it means the right part is also sorted. So now the left part is also sorted and right part is also sorted, So nums[low] will be the lowest
            return nums[low]
        else:
            # If the right part is not sorted, so the min will lie in that range (the unsorted range)
            low = mid + 1
return nums[low]

This is a perfect example of modifying binary search to solve more complex problems. You’ve captured the essence of the problem in your code. Good job!

What if I have if nums[mid] > nums[left]: left = mid + 1 instead of if nums[mid] > nums[right]: left = mid + 1?

If you replace if nums[mid] > nums[right]: left = mid + 1 with if nums[mid] > nums[left]: left = mid + 1, you might not get the correct answer in all scenarios.

The condition if nums[mid] > nums[right] checks whether the right half of the current array (from mid to right) is unsorted. If this condition is true, we know that the minimum value lies in the right half (which is unsorted), so we update left to mid + 1.

On the other hand, the condition if nums[mid] > nums[left] checks whether the left half of the current array (from left to mid) is sorted. If this condition is true, it does not necessarily mean that the minimum value lies in the right half. The minimum value could be nums[mid] itself or it could be in the right half. So, updating left to mid + 1 might exclude the actual minimum value from the search space, leading to incorrect results.

For example, consider the rotated array [4, 5, 1, 2, 3]. The minimum value is 1. If we start with left at index 0 and right at index 4, mid will be 2. nums[mid] (which is 1) is greater than nums[left] (which is 4), so if we update left to mid + 1 (which is 3), we exclude the actual minimum value (1) from our search space.

That’s why we use the condition if nums[mid] > nums[right]: left = mid + 1 instead of if nums[mid] > nums[left]: left = mid + 1 in the binary search algorithm for this problem.

Let’s break it down piece by piece.

First off, my code uses start instead of left and end instead of right. I didn’t estimate it to be that confusing to figure but alright.

Next, the name of the function is findPivotIndex, which I thought would indicate that it’s returning the ‘index of the pivot’ but anyway, I’ll tell ya now that that’s what it’s doing. So no, it is indeed intended to return start, not arr[start].

Now, I agree with you that your first comment was helpful, and I actually upvoted it. It was when you made the second comment that didn’t feel in place. And I was indeed replying to that but there are no sub-threads in this discussion interface unlike Disqus so it probably wasn’t clear straight away.

Anyway what yuhaozhang suggested originally wouldn’t be correct, but with the modification he later proposed it pretty much checks out. You commented back claiming not so but with a slight modification it does actually work, and your reply made it feel like ’no it’s impossible’.

The two original suggestions being:

if nums[mid] > nums[left]: left = mid + 1
if nums[0] < nums[length - 1]: return nums[0]

It actually checks out, there’s an = missing so the actual code would be:

if nums[mid] >= nums[left]: left = mid + 1

if nums[0] <= nums[length - 1]: return nums[0]

Without the = base cases like [2,1] for instance, would return one less than the actual pivot.

About not having run my code, yes I actually didn’t. I wrote it on the fly as a result of which, I missed the = in my second if check. if(arr[mid] > arr[start]) instead of if(arr[mid] >= arr[start]). I have run it now and it works as intended so feel free to give it a go.

Can some explain why nums[left] is returned instead of nums[right]

In the provided binary search approach, the variable right (or end) is adjusted in a way that it always points to an element that might still be the minimum. The variable left (or start), on the other hand, gets adjusted to only point at elements that are known not to be the minimum.

Let’s consider the following conditions:

  1. If nums[mid] > nums[right]: This implies that the minimum value lies in the right half of the considered array, because the middle value is greater than the rightmost value, suggesting a rotation. So, we shift left to mid+1, making right still point at a potential minimum.

  2. If nums[mid] <= nums[right]: This indicates the subarray from mid to right is sorted, and thus the minimum lies somewhere to the left of mid or at mid itself. So, we adjust right to mid, continuing to let right point at a potential minimum.

Therefore, at the end of this binary search process, right points to the minimum element in the array, and that’s why nums[right] is returned.

But why not return nums[left]?

Remember, left is adjusted to only point at elements that are known not to be the minimum. Thus, by the end of the binary search process, left is likely to point at an element that is not the minimum. This is why nums[left] is not returned.

When we break out of the loop, left and right no longer satisfy left < right, so we must have left >= right. Since we cannot have left > right (we begin the loop with left <= right, then you can verify that each loop will move left closer to right or right closer to left, but never left greater than right or right less than left), it must be the case that left == right after we exit the loop. So, nums[left] == nuts[right], and the choice to return nums[left] instead of nums[right] is arbitrary.

Yes, you’re absolutely correct. When the while loop terminates, it’s guaranteed that left and right are pointing at the same index (i.e., left == right), which is the index of the smallest element in the rotated sorted array.

So, indeed, we could return nums[left] or nums[right] at the end of the function - it would make no difference because they both reference the same element in the array. This is a great observation and shows you have a good understanding of how the algorithm works!

Can someone help explain the intuition and why we check nums[mid] > nums[right]? My solution checked nums[left] > nums[mid] and it doesn’t work.

Let’s look at two scenarios: one where the rotation is at a position i (i > 0) and the other where there is no rotation (i.e., a sorted array). We’ll use this to help understand why we compare nums[mid] and nums[right].

Scenario 1: Suppose we have an array nums = [4,5,6,7,0,1,2,3] where the smallest number is somewhere in the middle. The array is sorted but then rotated at some pivot unknown to us beforehand.

Now, in a standard binary search, we typically compare the mid element to the target and decide whether to go left or right. But here, we can’t do that directly because of the rotation. However, one property we can take advantage of is that when we calculate the mid index and compare the element at mid with the element at right:

  • If nums[mid] > nums[right], it means the smallest number is definitely in the right half (i.e., from mid + 1 to right), because we are in the “big” part of the rotation.

  • Otherwise, it means the smallest number is definitely in the left half (i.e., from left to mid), because we are in the “small” part of the rotation.

Scenario 2: Consider a sorted array nums = [1,2,3,4,5,6,7]. Here, no matter what mid we pick, nums[mid] is always <= nums[right], and we will always go left. This will eventually lead us to the smallest number.

If we compare nums[left] and nums[mid], in the first scenario, if nums[left] > nums[mid], we can’t be sure if the smallest number is in the left half or the right half. Therefore, comparing nums[mid] and nums[right] gives us a sure way to decide which direction to go.

I think the intuition would be to consider the array as 2 parts and reduce our search space on the following intuition, If we consider the rotation, we see that the minimum element is pushed to the right with each rotation. The property of a sorted array (assuming sort is in ascending order) is that the first element(here the middle) in the array would be less than last element in the array(the right) and this property would be broken when the min element crosses the middle position, which can be identified by comparing the middle and the right most element. That said we can observe that unless the min element crosses the middle index, all elements starting from the middle element to the end would be in sorted order (strictly increasing) in which case we always move the search space to the left of the middle element.

Yes, you’ve got it! When you’re performing a modified binary search on a sorted and rotated array, the key insight is to figure out which part of your current range (left half or right half) is sorted.

  1. If nums[mid] > nums[right], then the left half must be sorted. This is because the middle element is greater than the rightmost element, indicating that the pivot point (and therefore the smallest element) must be to the right of the middle. So, you update the left pointer to be mid + 1 to only search the unsorted half.

  2. If nums[mid] <= nums[right], then the right half must be sorted. This is because the middle element is less than or equal to the rightmost element, indicating that the pivot point must be to the left of the middle (or it could be the middle element itself if the middle element is the smallest one). So, you update the right pointer to be mid to only search the unsorted half.

By consistently reducing your search space to the unsorted half of the array, you’re able to find the smallest element in logarithmic time.

Let’s take an example to illustrate this:

Consider nums = [4, 5, 6, 7, 0, 1, 2]. The smallest number is 0. Initially, left = 0 and right = 6. The mid element is 7. Since nums[mid] > nums[right], we know the left half (from mid to right) is unsorted. So we update left to mid + 1.

Now left = 4 and right = 6. The mid element is 1. Since nums[mid] <= nums[right], we know the right half (from mid to right) is sorted. So we update right to mid.

Now left = 4 and right = 4. Since left equals right, we break out of the loop and return nums[left] which is indeed the smallest element.

Hope this helps!

how do you know with these type of questions: when to use while l < r and while l <= r?

Choosing between while l < r and while l <= r depends on the problem requirements and the exact condition you are using to terminate your binary search loop. Here’s a guideline that may help:

  1. while (l < r): This is typically used when the search space needs to keep reducing until there’s one element left. This is suitable for problems where you can derive the answer once you have narrowed down to one candidate element in your search space.

  2. while (l <= r): This is used when the search space should keep reducing until there are no elements left. This is suitable for problems where you’re directly searching for an element and want to conclude that the element is not found when there are no candidates left in your search space.

In this particular problem of finding the minimum element in a sorted and rotated array, while (l < r) is more suitable because the answer can be derived once we have one candidate left in the search space, and we want to avoid the risk of index overflow that can happen when we are directly accessing mid+1 or mid-1 without checking if mid is the last or the first index.

To generalize, the choice between l < r and l <= r depends on the nature of the problem and how you want to narrow down your search space. You may need to experiment a bit and go through some trial and error before finding out which one works for your specific problem.

Why to compare mid to right? but not left? basically whats your reasoning behind comparing to right?

We compare the mid element with the right element because of the properties of the problem itself. Let’s break it down:

  1. In a rotated sorted array, all elements to the right of the minimum element are less than the first element of the array.

  2. Thus, if the mid element is greater than the right element, we can conclude that the minimum element must be to the right of mid (since the array is rotated, the minimum has been moved to the right of mid). So we update left to mid + 1.

  3. If the mid element is less than or equal to the right element, it means that we’re in the “sorted” part of the array, and the minimum must be to the left of mid or at mid itself. So we update right to mid.

Comparing mid to left wouldn’t give us these insights because the “sortedness” and “rotatedness” of the array give us a relationship between the mid element and the right element, not between mid and left. Hence we compare mid to right, not to left.

If we add below early returns, the speed will be improved further:

    if (nums[mid] > nums[mid + 1])
        return nums[mid + 1];
    
    if (mid && nums[mid] < nums[mid - 1])
        return nums[mid];

Yes, these early return statements do optimize the function by reducing the number of iterations of the loop.

The condition nums[mid] > nums[mid + 1] is checking if the current middle element is greater than its next element, which means we have found the point of rotation (since in a sorted and rotated array, the point of rotation is the only place where the next element is smaller than the current element). So, we can return the next element which is the smallest.

Similarly, the condition mid && nums[mid] < nums[mid - 1] is checking if the current middle element is less than its previous element (making sure mid is not the first element), which also indicates the point of rotation, and hence we can return the current mid element as it is the smallest in the array.

These conditions effectively help us find the point of rotation in the array more quickly, which results in an overall speed improvement for the function. It’s a good demonstration of how understanding the problem’s properties can lead to further optimizations.

Can’t we traverse the list in reverse order like this

    if len(nums)==1:
        return nums[0]

    for i in range(len(nums)-1,-2,-1):
        if nums[i-1]>nums[i]:
            return nums[i]
    
    return nums[0]

Yes, you can definitely solve the problem by traversing the list in reverse order as you’ve shown. The code you wrote iterates from the end of the list to the start and checks if the previous number is greater than the current one. If it is, it means we’ve found the pivot point, and hence the minimum number.

However, please note that this solution has a time complexity of O(n), as in the worst-case scenario, you will have to iterate through all the elements in the array.

In contrast, a binary search-based solution, like the one proposed earlier, has a time complexity of O(log n), which is more efficient for large inputs. Binary search can take advantage of the sorted nature of the input list to eliminate half of the remaining elements in each step.

This is why for problems like these, where the input list is sorted (or partly sorted like in this case), a binary search-based solution is usually preferred over linear traversal, as it is more efficient.

Why is the same solution doesn’t work in JavaScript for [2,1] test case? Any pointers on this?

Here is an example of how you might implement this in JavaScript:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var findMin = function(nums) {
    let left = 0, right = nums.length - 1;
    while (left < right) {
        const mid = left + Math.floor((right - left) / 2);
        if (nums[mid] > nums[right]) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    return nums[left];
};

This should work for the [2,1] test case as well. The main idea is the same as the Python solution: we set our pointers left and right to the start and end of the array, then we continuously narrow down our search space by moving the left pointer to the right or the right pointer to the left depending on whether the middle element is larger or smaller than the rightmost element. This allows us to find the pivot point where the sorted array has been rotated.

If you’re still having trouble with the JavaScript version of this solution, the problem might lie somewhere else in your code. Double-check your implementation and make sure you’re correctly updating your pointers and conditions.

what would this algorithm look like if I wanted to return nums[mid] ?

In order to return nums[mid], you need to have an extra check inside your while loop to identify the condition when nums[mid] is the desired result. The exact condition would depend on the specific requirements of the problem.

For example, in the context of the problem 153. “Find Minimum in Rotated Sorted Array”, you could return nums[mid] when it’s less than both nums[mid - 1] and nums[mid + 1]. Here’s how it might look like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def findMin(nums):
    if len(nums) == 1:
        return nums[0]
    left, right = 0, len(nums) - 1
    while left < right:
        mid = left + (right - left) // 2
        if mid > 0 and mid < len(nums) - 1 and nums[mid] < nums[mid - 1] and nums[mid] < nums[mid + 1]:
            return nums[mid]
        if nums[mid] > nums[right]:
            left = mid + 1
        else:
            right = mid
    return nums[left]

But remember this is specific to the problem where we are finding a minimum in a rotated sorted array. In a different problem setting, the condition to return nums[mid] might be entirely different. It’s important to understand the problem’s requirements thoroughly and adjust your algorithm accordingly.

Great solution and explanation! Based on this logic, i have come up with findMax, seems need to add a condition to cover sorted array.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
static private int findMax(int[] arr) {
		int start = 0; 
		int end = arr.length - 1; 
		while (start < end) {
			int mid = start + (end - start) / 2; 
			if (arr[mid] >= arr[start] && arr[mid] < arr[end]) // whole array sorted
				return arr[end]; 
			else if (arr[mid] > arr[start]) // max is mid or on right of mid
				start = mid;
			else // max is on left of mid
				end = mid - 1;  
		}
		return arr[start]; 
	}

Yes, you’ve got the idea! The code you’ve written is mostly correct for finding the maximum value in a rotated sorted array. The logic is similar to finding the minimum value. However, there is a small mistake in your logic when dealing with the sorted array condition.

In your code, the condition arr[mid] >= arr[start] && arr[mid] < arr[end] is not exactly checking if the array is sorted or not. It’s checking if the middle element is greater than or equal to the start and less than the end, which may not necessarily mean that the array is sorted.

A better condition to check if the array is sorted might be arr[start] <= arr[end]. If this is true, then we can directly return arr[end] as the maximum.

Here is the corrected code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
static private int findMax(int[] arr) {
    int start = 0; 
    int end = arr.length - 1; 
    while (start < end) {
        int mid = start + (end - start) / 2; 
        if (arr[start] <= arr[end]) // whole array sorted
            return arr[end]; 
        else if (arr[mid] > arr[start]) // max is mid or on right of mid
            start = mid + 1;
        else // max is on left of mid
            end = mid;  
    }
    return arr[start]; 
}

Also, remember to handle the edge cases, such as an empty array, correctly. In this code, if the array is empty, it will throw an out-of-bounds exception.

can someone explain how you guys know when to put l <= r or l < r? I get confused in different problems about this condition.

The choice between using l < r or l <= r in the while loop of a binary search is dependent on the problem you’re solving. Both are valid but serve different purposes.

l < r: This version of binary search is usually used when the search space is continuous or when you want to find the smallest or largest number that fulfills a certain property. When the loop exits, l will be the smallest number that fulfills the property, and r will be the largest number that doesn’t fulfill the property.

l <= r: This version of binary search is usually used when the search space is discrete and you want to find an exact match. When the loop exits, r will be the largest number that fulfills the property, and l will be the smallest number that doesn’t fulfill the property.

That being said, it’s crucial to understand the nature of the problem and what the goal is before deciding on which condition to use in your while loop. One tip is to practice problems and understand how l < r and l <= r affect the search space and how they can help you find what you’re looking for. With enough practice, deciding which condition to use becomes intuitive.

how do we know that it should be left < right, not left <= right?

The left < right and left <= right conditions in the while loop of a binary search control how the search narrows and when it stops. Both can be used depending on the problem and what you’re trying to achieve.

left < right: This is used when you’re trying to find the smallest (or largest) number that fulfills a certain condition in a continuous search space. The loop continues until the search space is narrowed down to a single candidate. When the loop exits, left (or right) is the smallest number that satisfies the condition.

left <= right: This is used when you’re trying to find an exact match in a discrete search space. The loop continues until the search space is exhausted. If the target is in the search space, it will be found and returned. If the target is not in the search space, the function will return a “not found” indicator.

So, the choice between left < right and left <= right depends on whether you’re searching in a continuous or discrete search space and whether you’re looking for a condition satisfying number or an exact match. Understanding the problem requirements is key to making the right choice.

I guess you can add

            if nums[left] < nums[right]:
                return nums[left]

at the beginning of the loop in order to stop early

The optimization you suggested is valid in some situations, but it might not speed up the function significantly.

Here’s why:

Adding the condition if nums[left] < nums[right]: return nums[left] at the beginning of the loop would indeed allow us to exit early if the remaining portion of the array is sorted (i.e., not rotated). However, this condition requires a comparison at each step of the loop.

In a worst-case scenario, where the smallest element is at the very end of the array, this early exit wouldn’t happen until nearly the end of the search. So we wouldn’t save much time overall.

Moreover, the beauty of binary search is that it significantly reduces the search space with each iteration (by half), allowing us to search large arrays very efficiently. The time complexity of a binary search is O(log n), which is very efficient. So, even without the early exit, our function should be quite fast.

That being said, in some scenarios where you know that your data may often be nearly sorted, an early exit condition like this could be a helpful optimization. It all depends on the specific characteristics of your use case.

Similar Problems

Here are 10 problems on LeetCode that are similar to the “Find Minimum in Rotated Sorted Array” problem. The key concepts in this problem are binary search, arrays, and possibly divide-and-conquer:

  1. Search in Rotated Sorted Array (LeetCode #33): Similar to the original problem, this question involves a rotated sorted array. Instead of finding the minimum, you have to find a target element.

  2. Find Minimum in Rotated Sorted Array II (LeetCode #154): This is almost the same problem, but the array may contain duplicates. This makes the problem slightly more complex but the basic approach remains the same.

  3. Find Peak Element (LeetCode #162): This problem also requires a binary search approach but it asks for the peak element instead of the minimum element.

  4. Search in Rotated Sorted Array II (LeetCode #81): This problem extends “Search in Rotated Sorted Array” by allowing duplicates in the array.

  5. Search a 2D Matrix (LeetCode #74): In this problem, the sorted matrix can be visualized as a rotated sorted array. It requires the use of binary search in a slightly different context.

  6. Find First and Last Position of Element in Sorted Array (LeetCode #34): This problem deals with binary search and finding elements in a sorted array, similar to the given problem.

  7. Median of Two Sorted Arrays (LeetCode #4): Although the scenario is different, it requires similar thinking and binary search techniques.

  8. Search in a Sorted Array of Unknown Size (LeetCode #702): This problem also involves binary search on a sorted array.

  9. Find K Closest Elements (LeetCode #658): This problem requires finding elements in a sorted array based on their values. It’s a bit of a stretch but you might find similar binary search techniques useful here.

  10. Guess Number Higher or Lower II (LeetCode #375): This is a binary search problem with an added twist. Instead of searching for a specific element, you’re guessing a number between 1 and n.

In all these problems, understanding how to manipulate binary search or divide-and-conquer strategies is the key to finding the solution. Each of these problems requires a nuanced understanding of these concepts, and practicing them will give you a comprehensive understanding of such problems.

Practice Phase Template

  1. Problem understanding: Ensure you understand the problem thoroughly. If you’re uncertain, look up similar problems or revisit your course material. This problem asks to find the minimum element in a rotated sorted array.

  2. Understand the approach: This solution uses a binary search algorithm to find the minimum. The idea is to set two pointers, one at the beginning of the array and the other at the end, and then to converge these pointers towards the minimum element.

    Understand how the midpoint is calculated and the conditions that lead the pointers to move. Get clear on the reason why the left pointer is moved to the right (mid + 1) if the mid element is greater than the rightmost element, and why the right pointer is moved to the left (mid) if the mid element is less than or equal to the rightmost element.

  3. Code walkthrough: Walk through the code with a simple example. Use an example array like [4,5,6,1,2,3] and see how the pointers move and how the minimum element is found.

  4. Coding: Now, try to code the solution yourself. You can start by setting up the function and then writing the binary search algorithm.

    1
    2
    3
    4
    5
    6
    7
    
    def findMin(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        # TODO: Fill in your code here
        pass
    

    Test your function with the example array to verify it works as expected.

  5. Testing: After you’ve written the function, it’s important to test it with various test cases. Make sure to include edge cases. Here are a few to get you started:

    1
    2
    3
    4
    5
    
    print(findMin([4,5,6,1,2,3]))  # Expected: 1
    print(findMin([1,2,3,4,5]))  # Expected: 1
    print(findMin([5,1,2,3,4]))  # Expected: 1
    print(findMin([2,1]))  # Expected: 1
    print(findMin([2]))  # Expected: 2
    
  6. Review and improve: If all your test cases pass, review your code. Is there any part you could optimize or make more readable? If you find any, make the necessary changes and retest your code to ensure it still works.

The more you code, the more you’ll understand these concepts and be able to apply them in different contexts.

Targeted Drills

Drill 1: Understanding Python list slicing and manipulation

To get started with Python and the concept of arrays (represented as lists in Python), you need to understand how to create, manipulate, and access elements in a list.

1
2
3
4
5
6
7
8
9
# create a list
nums = [1, 2, 3, 4, 5]

# access elements
first = nums[0]
last = nums[-1]

# manipulate list
nums.append(6)  # adds 6 to the end of the list

Drill 2: Understanding the binary search algorithm

The binary search algorithm is a fundamental concept that is used in the problem solution. This algorithm is used to find a specific item in a sorted list.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def binary_search(arr, target):
    left, right = 0, len(arr) - 1

    while left <= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return -1

print(binary_search([1, 2, 3, 4, 5, 6], 4))  # Expected: index position of 4

Drill 3: Implementing the solution

After understanding the binary search algorithm, the next step is to modify it according to the problem’s requirements.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def find_min(nums):
    left, right = 0, len(nums) - 1

    while left < right:
        mid = (left + right) // 2

        if nums[mid] > nums[right]:
            left = mid + 1
        else:
            right = mid
    return nums[left]

print(find_min([4,5,6,1,2,3]))  # Expected: 1

Drill 4: Testing your solution

Finally, test your solution with different types of inputs to make sure it works in all scenarios.

1
2
3
4
5
print(find_min([4,5,6,1,2,3]))  # Expected: 1
print(find_min([1,2,3,4,5]))  # Expected: 1
print(find_min([5,1,2,3,4]))  # Expected: 1
print(find_min([2,1]))  # Expected: 1
print(find_min([2]))  # Expected: 2