[typescript] advanced summary of binary search algorithm (including rotation array)

Posted by dheeraj4uuu on Tue, 07 Dec 2021 12:44:48 +0100

On the question type of binary search

Ordinary dichotomy

  • LC704 binary search simple
  • LC34 finds the first and last positions of elements in a sorted array

Variants: rotating arrays

  • LC153 find the minimum value of rotation sort array
  • LC33 search rotation sort array medium

Dichotomous general skills

The most commonly used and basic binary search receives an array and a target target value. To find the target, return the index of the target. Return - 1 if not found. Answer directly corresponding to LC704

function search(nums: number[], target: number): number {
    let left: number = 0, right: number = nums.length - 1
    while (left <= right) {
        let mid: number = Math.floor(left + (right - left) / 2)
        if (nums[mid] < target){
            left = mid + 1
        } else if (nums[mid] > target){
            right = mid - 1
        } else if (nums[mid] === target){
            return mid
        }
    }
    return -1
};

Note:

  1. The main reason why mid = Math.floor((left + right)/2) is not used is that it is afraid to exceed the maximum value
  2. The left < = right in while is not simply less than because the area we need to search is a closed interval, that is [left,right], so that we can be sure not to miss a number

However, sometimes there are more than one target value, that is, there may be multiple target values. We need to find the left and right boundaries, so the above algorithm is unreliable, but some changes can still be made in the above algorithm. The main learning here is the idea mentioned in labuladong's algorithm script.

Suppose in a new array, [1,2,3,3,3,4], we want to find the left and right boundaries of target = 3. What should we do?

My solution in LC34 is directly posted here, more than 98% of the students

//main function
function searchRange(nums: number[], target: number): number[] {
    return [leftBound(nums,target), rightBound(nums,target)]
};

//find left bound
function leftBound(nums: number[], target: number): number {
    let left: number = 0, right: number = nums.length - 1
    while (left <= right){
        let mid: number = Math.floor(left + (right - left)/2)
        if (nums[mid] < target){
            left = mid + 1
        } else if (nums[mid] > target){
            right = mid - 1
        } else {
            right = mid - 1
        }
    }
    //Key parts
    if (left >= nums.length || nums[left] !== target) {
        return -1
    } else {
        return left
    }
}


//find right bound
function rightBound(nums: number[], target: number): number {
    let left: number = 0, right: number = nums.length - 1
    while (left <= right){
        let mid: number = Math.floor(left + (right - left)/2)
        if (nums[mid] < target){
            left = mid + 1
        } else if (nums[mid] > target){
            right = mid - 1
        } else {
            left = mid + 1
        }
    }
    //Key parts
    if (right < 0 || nums[right] !== target) {
        return -1
    } else {
        return right
    }
}

The meaning of this topic is very simple. Let's find the left and right boundaries. Let's find the left and right boundaries respectively. If you observe carefully, you will find that there is little difference from the basis, but there are two more lines of judgment results. Then, when num [mid] = = = target, we do not output, but narrow the boundary and continue to search.

The key part is to understand when the loop will jump out. The condition we give is left < = right. Therefore, when left > right, we can jump out of the loop.

First consider the function leftBound. When num [mid] is exactly equal to the left boundary, our right will be narrowed to the position of - 1 of the left boundary again, and then the left will continue to increase until it is equal to right. Finally, left = mid + 1 is just greater than right and equal to the position of the left boundary.

After jumping out of the loop, judge again because we did not consider two extreme cases: one is that the left exceeds the boundary, and the other is that the found left boundary is not equal to the target. In this way, the calculated result is correct.

Rotating array: variation of binary search

In leetcode, there are many questions about rotating arrays. Here are two to talk about. One is the most basic rotation array of LC153. For example, [3,4,5,0,1,2] is a typical rotation array. We can find the law, that is, except that the middle point is disordered, the others are orderly. Can we find the rotation point through binary search? (some students choose to traverse directly, either not, or when the amount of data is huge, the time complexity is very high.) in this topic, we only need to output 0, that is, the rotation point. Post answers directly

function findMin(nums: number[]): number {
    let left: number = 0, right: number = nums.length - 1
    while (left < right){
        let mid: number = Math.floor(left + (right - left) /2)
        if (nums[mid] < nums[right]){
            right = mid
        } else if (nums[mid] > nums[right]){
            left = mid + 1
        }
    }
    return nums[left]
};

Here is another pit point. Why is it left < right? Is an equal sign so different? I tried to change it to less than or equal, and then change it to other places. I found that I couldn't get the correct results. Let's analyze the reason carefully.

A problem mentioned earlier is to judge the exit condition of the cycle. Here, it is left === right. The cycle ends. The rotation point we want to find is the minimum value. Therefore, when mid happens to be the rotation point, it must be the first judgment to enter.

Why? Why can I be sure that mid must be smaller than right at this time? The first reason is Math.floor. The only condition for mid to be equal to right is left === right, so I quit the loop. So what we get next is mid = rotation point - 1. At this time, the corresponding value is the maximum value, so it must be greater than the previous right. Then left and right are equal, exit the loop and return to both left and right.

Therefore, if the binary search is to be discussed in great detail, the boundary conditions are very complex.

Finally, let's take a look at LC33. Searching the rotation sorting array is actually a combination of the rotation array and the classic template of binary search.

function search(nums: number[], target: number): number {
    const spin = findSpinIndex(nums)
    if (target < nums[spin] || target > nums[spin - 1]) {
        return -1
    } else if (target > nums[nums.length - 1]){
        return binarySearch(nums, target, 0, spin - 1)
    } else if (target <= nums[nums.length - 1]){
        return binarySearch(nums, target, spin, nums.length - 1)
    }
};

function findSpinIndex(nums:number[]): number {
    let left: number = 0, right: number = nums.length - 1
    while (left < right){
        let mid: number = Math.floor(left + (right - left) / 2)
        if (nums[mid] < nums[right]){
            right = mid
        } else if (nums[mid] > nums[right]){
            left = mid + 1
        }
    }
    return left
}

function binarySearch(nums: number[], target: number, leftBound: number, rightBound: number): number {
    let left: number = leftBound, right: number = rightBound
    while (left <= right){
        let mid: number = Math.floor(left + (right - left) / 2)
        if (nums[mid] > target){
            right = mid - 1
        } else if (nums[mid] < target){
            left = mid + 1
        } else if (nums[mid] === target) {
            return mid
        }
    } 
    return -1 
}

There is nothing special to pay attention to in this problem. Just pay attention to the boundary judgment of the main function

Topics: Javascript TypeScript Algorithm