来自左神的万能二分模板算法,再也不怕边界条件了

3,448 阅读3分钟

引言

博客封面.001.jpeg

二分查找算法是非常非常重要的一个算法,思路简单,但是边界条件不好把控,很容易考虑不周出错,本文将提供一种二分查找算法万能模板,该算法模板来自左神-yyds,简单清晰,针对任何二分边界问题都能手到擒来。

标准二分法

题目:给定一个已排序的数组 arrarr 及目标值 targettarget,返回 targettarget 在数组中的位置,不存在则返回 1-1

相信所有人闭着眼睛都能写出来,它是最基础的二分思想,边界条件考虑简单,于是写出如下的代码:

public static int search(int[] nums, int target) {            
    int left = 0, right = nums.length - 1;                    
    while (left <= right) {                                   
        //这个mid的位置是偏左的,当数组长度为奇数时候,mid为正中间,当数组长度为偶数时候,mid是靠近左边 
        int mid = left + ((right - left) / 2);                
        if (nums[mid] == target) {                            
            return mid;                                       
        } else if (nums[mid] < target) {                      
            left = mid + 1;                                   
        } else {                                              
            right = mid - 1;                                  
        }                                                     
    }                                                         
    return -1;                                                
}                                                             

难度太 Easy,现在让你在修改标准二分法,有如下要求:

  • 给定一个包含重复元素的排序数组 arrarr,请查找 targettarget 在数组中的左边界arr=[1,2,3,3,3,4,6,]arr = [1,2,3,3,3,4,6,]target=3target = 3 ,结果应该返回 22,如果不存在,请返回 1-1
  • 给定一个包含重复元素的排序数组 arrarr,请查找 targettarget 在数组中的右边界arr=[1,2,3,3,3,4,6,]arr = [1,2,3,3,3,4,6,]target=3target = 3 ,结果应该返回 44。如果不存在,请返回 1-1

如果你能快速写出来,说明你的二分法是过关的,边界条件把控的很好👍🏻。

如果你跟我一样,每次写二分都要卡一卡,没有一套标准的模板,此二分算法框架来自左神-yyds

查找左边界

题目:给定一个包含重复元素的排序数组 arrarr,请查找 targettarget 在数组中的左边界arr=[1,2,3,3,3,4,6,]arr = [1,2,3,3,3,4,6,]target=3target = 3 ,结果应该返回 22,如果不存在,请返回 1-1

public static int zuoShenSearchLeft(int[] arr, int target) {        
    int l = 0, r = arr.length - 1;                                  
    //记录最左边的位置,每次更新,注意此时的 while 循环是可以走到 l = r 的                     
    int index = -1;                                                 
    while (l <= r) {                                                
        int mid = l + ((r - l) >> 1);                               
        if (target <= arr[mid]) {                                   
            //左边界,继续往左找                                             
            index = mid;                                            
            r = mid - 1;                                            
        } else {                                                    
            l = mid + 1;                                            
        }                                                           
    }                                                               
    //没找到,此时返回 -1                                                   
    if (index == -1) return -1;                                     
    System.out.println(index);                                      
    return arr[index] == target ? index : -1;                       
}                                                                                                                                 

引申:给定一个排序数组 arr=[1,2,3,3,3,6,7,10,14]arr = [1, 2, 3, 3, 3, 6, 7, 10, 14]target=5target = 5 ,在 arrarr 中查找第一个比它小的数的位置。返回位置 index=4index = 4

这个边界查找在很多算法题中都有使用到,特别重要

public static int zuoShenSearchLower(int[] arr, int target) {  
    int l = 0, r = arr.length - 1;                             
    if (arr[r] < target) return r;                             
    //记录最左边的位置,每次更新,注意此时的 while 循环是可以走到 l = r 的                
    int index = -1;                                            
    while (l <= r) {                                           
        int mid = l + ((r - l) >> 1);                          
        //左边界,继续往左找                                            
        if (target <= arr[mid]) {                              
            index = mid;                                       
            r = mid - 1;                                       
        } else {                                               
            l = mid + 1;                                       
        }                                                      
    }                                                          
    //没找到,此时返回 -1                                              
    if (index == -1) return -1;                                
    return index - 1;                                          
}                                                                                                            

查找右边界

题目:给定一个包含重复元素的排序数组 arrarr,请查找 targettarget 在数组中的右边界arr=[1,2,3,3,3,4,6,]arr = [1,2,3,3,3,4,6,]target=3target = 3 ,结果应该返回 44,如果不存在,请返回 1-1

public static int zuoShenSearchRight(int[] arr, int target) {  
    int l = 0, r = arr.length - 1;                             
    //记录最右边的位置,及时更新                                            
    int index = -1;                                            
    while (l <= r) {                                           
        int mid = l + ((r - l) >> 1);                          
        //右边界,则继续往右找                                           
        if (target >= arr[mid]) {                              
            index = mid;                                       
            l = mid + 1;                                       
        } else {                                               
            r = mid - 1;                                       
        }                                                      
    }                                                          
    if (index == -1) return -1;                                
    return arr[index] == target ? index : -1;                  
}                                                              

引申:给定一个排序数组 arr=[1,2,3,3,3,6,7,10,14]arr = [1, 2, 3, 3, 3, 6, 7, 10, 14]target=8target = 8 ,在 arrarr 中查找第一个比它大的数的位置。返回位置 index=7index = 7

这个边界查找在很多算法题中都有使用到,特别重要

public static int zuoShenSearchUpper(int[] arr, int target) { 
    int l = 0, r = arr.length - 1;                            
    if (arr[r] < target) return -1;                           
    if (arr[0] > target) return 0;                            
    //记录最右边的位置,及时更新                                           
    int index = -1;                                           
    while (l <= r) {                                          
        int mid = l + ((r - l) >> 1);                         
        //右边界,则继续往右找                                          
        if (target >= arr[mid]) {                             
            index = mid;                                      
            l = mid + 1;                                      
        } else {                                              
            r = mid - 1;                                      
        }                                                     
    }                                                         
    if (index == -1) return -1;                               
    return index + 1;
}  

算法实战

1.局部最小值问题

题目:无序数组,任意两个相邻的数不相等,返回一个局部最小值。注意:这个局部最小值可能有多个,只要找到一个就可以。

分析

  • 如果一个数组(0 1)(0~1)是升序排列,则局部最小值是 0 位置
  • 如果一个数组(n2,n1)(n-2, n-1)是降序排列,则局部最小值是 n1n - 1 位置
  • 如果上述都不满足,那这个局部最小位置一定在中间。
public static int getLessIndex(int[] arr) {               
    if (arr == null || arr.length < 1) return -1;         
    //1.(0~1)是升序                                          
    if (arr.length == 1 || arr[0] < arr[1]) return arr[0];
    //2.(n-2,n-1)是降序                                      
    int n = arr.length;                                   
    if (arr[n - 2] > arr[n - 1]) return arr[n - 1];       
    //3.局部最小值在中间位置,此时用二分查找                                
    int l = 1, r = n - 2;                                 
    while (l < r) {                                       
        int mid = (l + r) / 2;                            
        if (arr[mid] > arr[mid - 1]) {                    
            //左边升序                                        
            r = mid - 1;                                  
        } else if (arr[mid] > arr[mid + 1]) {             
            //右边降序                                        
            l = mid + 1;                                  
        } else {                                          
            return mid;                                   
        }                                                 
    }                                                     
    return l;                                             
}                                                         

2.在排序数组中查找元素的第一个和最后一个位置

image-20220419231150283.png

class Solution {

    public int[] searchRange(int[] nums, int target) {
        if(nums == null || nums.length == 0) return new int[]{-1,-1};
        int n = nums.length;
        if(nums[0] > target || nums[n - 1] < target) return new int[]{-1,-1};
        int left = getLeftBounds(nums,target);
        int right = getRightBounds(nums,target);
        return new int[]{left,right};
    }

    public int getLeftBounds(int[] nums,int target){
        int n = nums.length;
        int l = 0, r = n - 1;
        int index = -1;
        while(l <= r){
            int m = l + (r - l) / 2;
            if(target <= nums[m]){
                index = m;
                r = m - 1;
            }else{
                l = m + 1;
            }
        }
        if(index == -1) return -1;
        return  nums[index] == target ? index : -1; 
    }

     public int getRightBounds(int[] nums,int target){
        int n = nums.length;
        int l = 0, r = n - 1;
        int index = -1;
        while(l <= r){
            int m = l + (r - l) / 2;
            if(target >= nums[m]){
                index = m;
                l = m + 1;
            }else{
                r = m - 1;
            }
        }
        if(index == -1) return -1;
        return  nums[index] == target ? index : -1; 
    }

}

3. 搜索插入位置

image-20220419231927143.png

思路:这不就是找 targettarget 在排序数组中的位置。

class Solution {

    public int searchInsert(int[] nums, int target) {
        if(nums == null || nums.length == 0) return 0;
        int n = nums.length;
        if(nums[0] > target) return 0;
        if(nums[n - 1] < target) return n;
        int l = 0, r = n - 1;
        int index = -1;
        while(l <= r){
            int m = l + (r - l) / 2;
            if(target <= nums[m]){
                index = m;
                r = m - 1;
            }else{
                l = m + 1;
            }
        }
        return index;
    }
}

4. 寻找峰值

image-20220419232713886.png

class Solution {

    /**
     * 寻找峰值,即凸位置
     * label:二分查找,判断 m 的左边和右边
     * 1.如果 num[0] > num[1] 则直接返回,num[n-2] < num[n-1] 直接返回
     * 2.否则二分查找
     */
    public int findPeakElement(int[] nums) {
        if (nums == null || nums.length == 0) return -1;
        int n = nums.length;
        if (n == 1) return 0;
        if (nums[0] > nums[1]) return 0;
        if (nums[n - 2] < nums[n - 1]) return n - 1;
        int l = 1, r = n - 2;
       //注意 while 条件
        while (l < r) {
            int m = (l + r) / 2;
            if (nums[m] > nums[m - 1] && nums[m] > nums[m + 1]){
                return m;
            }else if (nums[m] < nums[m - 1]){
                r = m - 1;
            }else {
                l = m + 1;
            }
        }
        return l;
    }
}

结语

学习是枯燥而孤独的

无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事,而不是让内心烦躁、焦虑,毁掉你本就不多的热情和定力。