二分查找算法复习

Posted 小智RE0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二分查找算法复习相关的知识,希望对你有一定的参考价值。

文章目录


基础复习


首先二分查找算法是要基于当前数组为有序数组;
实际就是每次找中心点的值; 若目标数等于中心点了,直接返回即可,若目标数小于中间数,则在中心点的左侧查找,反之则在中心点的右边查找.

在实际的使用中,二分查找可以用不同模板的方式实现,那么具体在判断中心点时的规则就不一样.

从经典的基本问题入手;力扣原题704. 二分查找;

class Solution 
    public int search(int[] nums, int target) 
       //基本二分查找模板;
       int n = nums.length;
       if(n < 2) return nums[0] == target ?0:-1;

       int left  = 0;
       int right = n - 1;
       while(left<=right)
           //计算中点;
           //int middle = (right + left)/2;在不考虑溢出情况时可以用这个;
           int middle = left + (right-left)/2;
           if(nums[middle] == target)
               return middle;
           else if(nums[middle] > target)
               right = middle -1;
           else
               left = middle +1;
           
       
       return -1; 
    


实际稍微改变条件后,也可以这样写

class Solution 
    public int search(int[] nums, int target) 
       //基本二分查找模板;
       int n = nums.length;
       if(n < 2) return nums[0] == target ?0:-1;

       int left  = 0;
       int right = n - 1;
       while(left<right)
           //计算中点;
           //int middle = (right + left)/2;在不考虑溢出情况时可以用这个;
           int middle = left + (right-left)/2;
           if(nums[middle] >= target)
               right = middle;
           else
               left = middle +1;
           
       
       //判断是否存在;
       if(nums[left] == target)
           return left;
       
       return -1; 
    


注意,如果边界条件是 left(左指针) = middle(中心点);时;
那么计算中心点需要(+1 )向上取整 ;
这是由于在数组区间仅有两个数字的时候,防止出现计算的中心点一直是左节点,导致不能退出循环;

class Solution 
    public int search(int[] nums, int target) 
       //基本二分查找模板;
       int n = nums.length;
       if(n < 2) return nums[0] == target ?0:-1;

       int left  = 0;
       int right = n - 1;
       while(left<right)
           //计算中点;
           //int middle = (right + left + 1)/2;在不考虑溢出情况时可以用这个;
           int middle = left + (right-left)/2 +1;
           if(nums[middle] > target)
               right = middle - 1;
           else
               left = middle;
           
       
       //判断是否存在;
       if(nums[left] == target)
           return left;
       
       return -1; 
    


或者将循环的判断条件变为left +1 < right
由于最后会留下两个元素,注意需要在退出循环后进行两次判断;

class Solution 
    public int search(int[] nums, int target) 
       //基本二分查找模板;
       int n = nums.length;
       if(n < 2) return nums[0] == target ?0:-1;

       int left  = 0;
       int right = n - 1;
       while(left+1<right)
           //计算中点;
           int middle = left + (right-left)/2;
           if(nums[middle] == target)
               return middle;
           else if(nums[middle] < target)
               left = middle;
           else
               right = middle;
           
       
       //判断是否存在;
       if(nums[left] == target)
           return left;
       
       if(nums[right] == target)
           return right;
       

       return -1; 
    


练习


力扣35题-搜索插入位置

原题位置:35. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。
如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。

示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
    
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
    
示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4
    
示例 4:
输入: nums = [1,3,5,6], target = 0
输出: 0
    
示例 5:
输入: nums = [1], target = 0
输出: 0

提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums 为无重复元素的升序排列数组
-104 <= target <= 104

(1)双指针

class Solution 
    public int searchInsert(int[] nums, int target) 
        int n = nums.length;
        //由于这是已经排序的数组,则可以直接尾插;
        if(nums[n-1] < target) return n;

        //简易双指针;
        int left = 0;
        int right = n-1;
        while(left < right)
            int middle = left + (right-left)/2;
            if(nums[middle] < target)
                left = middle +1;
            else
                right = middle;
            
        
        //由于绝对可以插入,则直接返回这个位置;
        return left;
    

(2)之前写的双指针思路

class Solution 
    public int searchInsert(int[] nums, int target) 
        if(nums.length == 0) return 0;
        List<Integer> list = new ArrayList<>();
        for(int i:nums)
            list.add(i);
        

        //二分查找类型的题目;先定义左右指针;
        int left = 0;
        int right = nums.length-1;
        //中心值;
        int middle = 0;
        while(left<=right)
            middle = left + (right - left)/2;
            if(nums[middle] == target)
                  return middle;

            //若中心点大于目标值,则在左边查找;      
            if(nums[middle] > target)
                right = middle - 1;
            
            //若小于目标值,则在右边查找;
            if(nums[middle] < target)
                left = middle+1;
            
        
        //若没找到,则返回新的添加位置;
        return right+1;
    

(3)使用辅助变量记录

class Solution 
    public int searchInsert(int[] nums, int target) 
        int n = nums.length;
        //由于这是已经排序的数组,则可以直接尾插;
        if(nums[n-1] < target) return n;

        //简易双指针;
        int left = 0;
        int right = n-1;
        //辅助变量;
        int res = 0;
        while(left <= right)
            int middle = left + (right-left)/2;
            if(nums[middle] >= target)
                 res = middle;
                 right = middle -1;
            else
                 left = middle +1;
            
        
        return res;
    


力扣34题:在排序数组中查找元素的第一个和最后一个位置

原题位置:34. 在排序数组中查找元素的第一个和最后一个位置
题目要求

给定一个按照升序排列的整数数组 nums,和一个目标值 target。
找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:
你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
 
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:
输入:nums = [], target = 0
输出:[-1,-1]
 
提示:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums 是一个非递减数组
-109 <= target <= 109

使用两次二分查找;
注意使用循环判断条件为 left < right 时;
由于本题需要查询第一次出现的位置和最后一次出现的位置;
则第一次出现位置查询 时 注重于查询左侧,
但最后一次出现位置查询时注重于查询右侧;
还要注意之前提到的边界情况为left = middle;的循环退出问题,需要向上取整;

class Solution 
    public int[] searchRange(int[] nums, int target) 
        int[] res = new int[2];
        int n = nums.length;
        //排除特殊情况;
        if(n == 0) 
           return new int[]-1,-1;

        //使用二分查找的方法,注意需要使用两次;
        int start = binarySearchStart(nums,target);
        int end   = binarySearchEnd(nums,target);
        res[0] = start;
        res[1] = end;
        return  res;
    

    //查找第一次出现的元素位置;
    private int binarySearchStart(int[] nums,int target)
        int left  = 0;
        int right = nums.length-1;
        while(left < right )
            //中心点;
            int middle = left + (right-left)/2;
            if(target > nums[middle])
                //右侧;
                left = middle +1;
            else
                //target <= nums[middle]
                //左侧存在;
                right = middle;
            
        
        //由于无法确定是否存在,这里需要判定;
        if(nums[left] == target)
            return left;
         
        return -1;
    

    //查找最后一次出现的位置;
    private int binarySearchEnd(int[] nums,int target)
        int left  =0;
        int right =nums.length-1;
        while(left < right)
            //中心点,这里主要注意防止死循环问题,需要向上取整;
            int middle = left + (right - left )/2 +1;
            if(target < nums[middle])
                //左侧;
                right = middle - 1;
            else
                //target >= nums[middle];
                //右侧存在;
                left = middle;
            
        
        //由于无法确定是否存在,这里需要判定;
        if(nums[left] == target)
            return left;
         
        return -1;
    


力扣744题:寻找比目标字母大的最小字母

原题位置:744. 寻找比目标字母大的最小字母

给你一个排序后的字符列表 letters ,列表中只包含小写英文字母。
另给出一个目标字母 target,请你寻找在这一有序列表里比目标字母大的最小字母。
在比较时,字母是依序循环出现的。举个例子:
如果目标字母 target = 'z' 并且字符列表为 letters = ['a', 'b'],则答案返回 'a'
 

示例:

输入:
letters = ["c", "f", "j"]
target = "a"
输出: "c"

输入:
letters = ["c", "f", "j"]
target = "c"
输出: "f"

输入:
letters = ["c", "f", "j"]
target = "d"
输出: "f"

输入:
letters = ["c", "f", "j"]
target = "g"
输出: "j"

输入:
letters = ["c", "f", "j"]
target = "j"
输出: "c"

输入:
letters = ["c", "f", "j"]
target = "k"
输出: "c"
 

提示:
letters长度范围在[2, 10000]区间内。
letters 仅由小写字母组成,最少包含两个不同的字母。
目标字母target 是一个小写字母。

使用二分查找;
注意若找的目标字母不在数组中,返回数组的第一个字母;

class Solution 
    public char nextGreatestLetter(char[] letters, char target) 
        int n = letters.length;
        //若没有或者是最后一位,直接返回第一个字母;
        if(letters[n-1] <= target)
            return letters[0];
        

        //标准二分查找;
        int left = 0;
        int right = n-1;
        while(left < right)
            int middle = left + (right - left) /2;
            if(target >= letters[middle])
                left = middle+1;
            else
                right = middle;
            
        
        return letters[left];
    


力扣275题:H 指数 II

原题位置:275. H 指数 II
题目说明:

给你一个整数数组 citations ,
其中 citations[i] 表示研究者的第 i 篇论文被引用的次数,
citations 已经按照 升序排列 。
计算并返回该研究者的 h 指数。

h 指数的定义:h 代表“高引用次数”(high citations),
一名科研人员的 h 指数是指他(她)的 (n 篇论文中)
总共有 h 篇论文分别被引用了至少 h 次。
且其余的 n - h 篇论文每篇被引用次数 不超过 h 次。

提示:如果 h 有多种可能的值,h 指数 是其中最大的那个。
请你设计并实现对数时间复杂度的算法解决此问题。
 
示例 1:
输入:citations = [0,1,3,5,6]
输出:3 
解释:
给定数组表示研究者总共有 5 篇论文,
每篇论文相应的被引用了 0, 1, 3, 5, 6 次。
由于研究者有 3 篇论文每篇 至少 被引用了 3 次,
其余两篇论文每篇被引用 不多于 3 次,所以她的 h 指数是 3 。
     
示例 2:
输入:citations = [1,2,100]
输出:2
 
提示:
n == citations.length
1 <= n <= 105
0 <= citations[i] <= 1000
citations 按 升序排列

H指数 = res, 论文引用次数至少使用了当前 res 个的 论文数量;
实际就是将论文作为一个区间;找到普遍引用高的论文开始位置;
最终计算出高引用量的论文数量;

class Solution 
    public int hIndex(int[] citations算法复习:二分查找

算法知识常用算法详解丨二分查找法(折半查找)

复习一下数据结构——无序查找二分查找斐波那契分割查找

查找算法(二分查找)

测试不得不懂的算法_01_二分查找算法

算法二分法 ② ( 排序数组中查找目标值 | 二分法的经典写法 | 在排序数组中查找元素的最后一个位置 | 二分法的通用模板 )