Dichotomy details, the choice of boundaries is distressing

Posted by bodge on Wed, 10 Nov 2021 22:07:13 +0100


Reference link: https://blog.csdn.net/xiao_jj_jj/article/details/106018702
Dichotomy finding complexity usage scenarios

1, Find whether to include a number

prerequisite:
1. Ordered sequence
2. No duplicate numbers

Example: force buckle 704
Given an n-element ordered (ascending) integer array nums and a target value target, write a function to search the target in nums. If the target value exists, return the subscript, otherwise return - 1.

Example 1:
input: nums = [-1,0,3,5,9,12], target = 9     
output: 4       
explain: 9 Appear in nums Middle and subscript 4     
Example 2:

input: nums = [-1,0,3,5,9,12], target = 2     
output: -1        
explain: 2 non-existent nums So return -1      

Tips:

You can assume that all elements in nums are not repeated.
n will be between [1, 10000].
Each element of num will be between [- 9999, 9999]

// Version one
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1; // Define that the target is in the left closed and right closed interval, [left, right]
        while (left <= right) { // When left==right, the interval [left, right] is still valid, so use<=
            int middle = left + ((right - left) / 2);// Preventing overflow is equivalent to (left + right)/2
            if (nums[middle] > target) {
                right = middle - 1; // target is in the left interval, so [left, middle - 1]
            } else if (nums[middle] < target) {
                left = middle + 1; // target is in the right range, so [middle + 1, right]
            } else { // nums[middle] == target
                return middle; // Find the target value in the array and return the subscript directly
            }
        }
        // Target value not found
        return -1;
    }
};

// Version 2
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size(); // Define the target in the left closed right open interval, i.e. [left, right)
        while (left < right) { // When left == right, it is invalid in [left, right], so it is used<
            int middle = left + ((right - left) >> 1);
            if (nums[middle] > target) {
                right = middle; // target is in the left interval, in [left, middle]
            } else if (nums[middle] < target) {
                left = middle + 1; // target is in the right interval, in [middle + 1, right)
            } else { // nums[middle] == target
                return middle; // Find the target value in the array and return the subscript directly
            }
        }
        // Target value not found
        return -1;
    }
};

The conditions of the while loop are < =, <?
Because the assignment of initialization right is num.length-1, that is, the index of the last element, not num.length.

The two may appear in binary search of different functions. The difference is that the former is equivalent to the interval closed at both ends [left, right], and the latter is equivalent to the interval closed at both ends [left, right], because the index size of num.length is out of bounds.

In our algorithm, we use the interval with both ends of the former [left, right] closed. This interval is actually the interval for each search, which we might as well call "search interval".

When should the search be stopped? Of course, it can be terminated when the target value is found:

if(nums[mid] == target)
    return mid;

However, if it is not found, the while loop needs to be terminated, and then return - 1. When should the while loop be terminated? When the search interval is empty, it should be terminated, which means you can't find it, which means you can't find it.

The termination condition of while (left < = right) is left == right + 1, which is written in the form of [right + 1, right], or take a specific number into [3, 2]. It can be seen that the search interval is empty at this time, because there are no numbers greater than or equal to 3 and less than or equal to 2. Therefore, the termination of while loop is correct at this time, and - 1 can be returned directly.

The termination condition of while (left < right) is left == right, which is written as an interval in the form of [left, right], or take a specific number into [2, 2]. At this time, the search interval is not empty, and there is a number 2, but at this time, the while loop terminates. That is to say, the interval [2, 2] is omitted, and index 2 is not searched. If - 1 is returned directly at this time, it is wrong.

Of course, if you have to use while (left < right), we already know the reason for the error, so we can patch it:

//...
while(left < right) {
    // ...
}
return nums[left] == target ? left : -1;

Why left = mid + 1, right = mid - 1? I think some codes are right = mid or left = Mid. without these additions and subtractions, what's going on and how to judge?

**A: * * this is also a difficulty in binary search, but as long as you can understand the previous content, you can easily judge.

The concept of "search interval" was defined just now, and the search interval of this algorithm is closed at both ends, i.e. [left, right]. So when we find that the index mid is not the target, how to determine the next search interval?

Of course [left, mid - 1] or [mid + 1, right], right? Because mid has been searched, it should be removed from the search interval.

2, Find left or right boundary

This indicates that there may be duplicate numbers, and the method mentioned in part above is not appropriate.
It needs to be divided into left boundary

int left_bound(int[] nums, int target) {
    if (nums.length == 0) return -1;
    int left = 0;
    int right = nums.length; // be careful
    
    while (left < right) { // be careful
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            right = mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid; // be careful
        }
    }
    return left;
}
 

Why is left = mid + 1 and right = mid different from the previous algorithm?

A: This is easy to explain. Because our "search interval" is [left, right] closed on the left and open on the right, when num [mid] is detected, the next search interval should remove the mid and be divided into two intervals, namely [left, mid) or [mid + 1, right).

Why can the algorithm search the left boundary?

A: the key is to deal with the case of num [mid] = = target:

if (nums[mid] == target)
    right = mid;

It can be seen that when you find the target, do not return immediately, but narrow the upper bound of the "search interval" right, and continue searching in the interval [left, mid), that is, constantly shrink to the left to lock the left boundary.

Why return left instead of right?

A: they are all the same, because the condition of while termination is left == right.

right border

int right_bound(int[] nums, int target) {
    if (nums.length == 0) return -1;
    int left = 0, right = nums.length;
    
    while (left < right) {
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            left = mid + 1; // be careful
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid;
        }
    }
    return left - 1; // be careful
}

Why does it return left - 1 instead of left as the function of the left boundary? And I think right should be returned since it is searching the right boundary.

A: first, the termination condition of the while loop is left == right, so left and right are the same. You have to reflect the characteristics of the right side and return right - 1.

Example: force buckle 34
Give an integer array nums in ascending order and a target value target. Find the start and end positions of the given target value in the array.

If the target value target does not exist in the array, return [- 1, - 1].

Advanced:

Can you design and implement an algorithm with time complexity of O(log n) to solve this problem?

Example 1:

Input: nums = [5,7,7,8,8,10], target = 8
 Output:[3,4]
Example 2:

Input: nums = [5,7,7,8,8,10], target = 6
 Output:[-1,-1]
Example 3:

Input: nums = [], target = 0
 Output:[-1,-1]
#Methods mentioned above
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {      
    if (nums.empty()) return {-1,-1};
    int left = 0;
    int right = nums.size()-1 ; // Note that you need to subtract one here, otherwise there may be overflow problems
    
    while (left < right) { // be careful
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            right = mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid; // be careful
        }
    }
    if(nums[right]!=target) return {-1,-1};
        int L=right;    
         left = 0, right = nums.size() ;
    
    while (left < right) {
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            left = mid + 1; // be careful
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid;
        }
    }
    return {L,left-1};
    }
};


#Another template
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {      
        if(nums.empty()) return {-1,-1};
        int left=0,right=nums.size()-1;
        while(left<right){
            int mid=(left+right)/2;
            if(nums[mid]>=target) right=mid;
            else left=mid+1;
        }
        if(nums[right]!=target) return {-1,-1};
        int L=right;
        left=0,right=nums.size()-1;
        while(left<right){
            int mid=(left+right+1)/2;
            if(nums[mid]<=target) left=mid;
            else right=mid-1;
        }
        return {L,right};
    }
};

Topics: C++ Algorithm leetcode