二分查找算法/折半查找

1. 二分查找

  • 适用前提:待查找序列有序;
  • 时间复杂度:O(log2n);

在这里插入图片描述
代码框架:

int binarySearch(int[] nums, int target) {
    
    
    int left = 0, right = ...;

    while(...) {
    
    
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
    
    
            ...
        } else if (nums[mid] < target) {
    
    
            left = ...
        } else if (nums[mid] > target) {
    
    
            right = ...
        }
    }
    return ...;
}

无论使用二分搜索是查找单个元素还是搜索边界,最本质的点在于保证搜索区间的合法性

1.1 二分搜索单个元素

  • 查找结果:1)没找到返回-1;2)找到返回其对应位置索引;
  • 左右指针分别指向数组最左端、最右端位置,初始搜索区间为[left,right];
  • 在while (left<=right)时,搜索终止条件为left>right,此时搜索区间为[left,right]=[right+1,right],区间为空,说明找不到;
  • 在while (left<right)时,搜索终止条件为left=right,此时搜索区间为[left,right]=[right,right),区间为空,说明找不到;

代码框架:

    public static int binarySearch(int[] arr,int target){
    
    
        int left=0,right=arr.length-1;
        while (left<=right){
    
    
            int mid=left+(right-left)/2;
            if (arr[mid]==target){
    
    
                return mid;
            }else if (arr[mid]<target){
    
    
                left=mid+1;
            }else if (arr[mid]>target){
    
    
                right=mid-1;
            }
        }
        return -1;
    }

    // 递归方式
    public static int binarySearch(int[] arr,int target,int left,int right){
    
    
        if (left>right){
    
    
            return -1;
        }
        int mid=left+(right-left)/2;
        if (arr[mid]==target){
    
    
            return mid;
        }else if (arr[mid]<target){
    
    
            return binarySearch(arr,target,mid+1,right);
        }else if (arr[mid]>target){
    
    
            return binarySearch(arr,target,left,mid-1);
        }
        return -1;
    }

在这里插入图片描述

1.2 二分搜索左侧边界

  • 序列中某个元素可能有连续多个,此方法可用于确定最左侧的该元素;
  • target可能极大,导致left一直右移,可能越界,所以最后需要对l是否越界进行判断;
  • [l,r]=[l,mid-1],此时大概率arr[mid]=target,当l>r导致搜索终止时,说明l=mid;

代码框架:

    // 查找左边界
    public static int searchLeft(int[] arr,int target){
    
    
        int l=0,r=arr.length-1;  // [l,r]
        // 终止条件l>r
        while (l<=r){
    
    
            int mid= l+(r-l)/2;
            if (arr[mid]==target){
    
    
                // 收缩右边界,锁定左边界
                r=mid-1;
            }else if (arr[mid]<target){
    
      // mid+-1取决于当前模式是否已经搜索过mid自身
                l=mid+1;
            }else if (arr[mid]>target){
    
    
                r=mid-1;
            }
        }
        // target可能极大,导致left一直右移,可能越界
        if (l==arr.length){
    
    
            return -1;
        }
        // [l,r]=[l,mid-1],此时大概arr[mid]=target,当l>r导致搜索终止时,说明l=mid
        return arr[l]==target?l:-1;
    }

1.2 二分搜索右侧边界

  • 序列中某个元素可能有连续多个,此方法可用于确定最右侧的该元素;
  • target可能极小,导致right一直左移,可能越界,此时r<0,而l>r -> l=r+1 -> l-1=r,所以r<0可写为l-1<0;
  • [l,r]=[mid+1,r],此时大概arr[mid]=target,当l>r导致搜索终止时,说明l-1=mid;

代码框架:

    // 查找右边界
    public static int searchRight(int[] arr,int target){
    
    
        int l=0,r=arr.length-1;  // [l,r]
        // 终止条件l>r
        while (l<=r){
    
    
            int mid= l+(r-l)/2;
            if (arr[mid]==target){
    
    
                // 收缩左边界,锁定右边界
                l=mid+1;
            }else if (arr[mid]<target){
    
      // mid+-1取决于当前模式是否已经搜索过mid自身
                l=mid+1;
            }else if (arr[mid]>target){
    
    
                r=mid-1;
            }
        }
        // target可能极小,导致right一直左移,可能越界,此时r<0,而l>r  ->  l=r+1  -> l-1=r,所以r<0可写为l-1<0
        if (l-1<0){
    
    
            return -1;
        }
        // [l,r]=[mid+1,r],此时大概arr[mid]=target,当l>r导致搜索终止时,说明l-1=mid
        return arr[l-1]==target?l-1:-1;
    }

2.力扣题

2.1 力扣704. 二分查找

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。链接:https://leetcode.cn/problems/binary-search

解题思路:这道题是最原始的在有序数组中查找单个元素的问题,直接使用代码框架即可;

class Solution {
    
    
    // 二分查找
    public int search(int[] nums, int target) {
    
    
        int l=0,r=nums.length-1;  // 搜索区间两端为闭区间
        while(l<=r){
    
      // 加等号
            int mid=l+(r-l)/2;
            if(nums[mid]==target){
    
    
                return mid;
            }else if(nums[mid]<target){
    
    
                l=mid+1;
            }else if(nums[mid]>target){
    
    
                r=mid-1;
            }
        }
        return -1;
    }

    // 线性查找
    // public int search(int[] nums, int target) {
    
    
    //     for(int i=0;i<nums.length;i++){
    
    
    //         if(target==nums[i]){
    
    
    //             return i;
    //         }
    //     }
    //     return -1;
    // }
}

2.2 剑指 Offer II 073. 狒狒吃香蕉

狒狒喜欢吃香蕉。这里有 n 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 h 小时后回来。狒狒可以决定她吃香蕉的速度 k (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 k 根。如果这堆香蕉少于 k 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉,下一个小时才会开始吃另一堆的香蕉。 狒狒喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。返回她可以在 h 小时内吃掉所有香蕉的最小速度 k(k 为整数)。
链接:https://leetcode.cn/problems/nZZqjQ/

分析

  • 不考虑时间限制,如果速度为所有香蕉堆中香蕉数量最大的一个,比如maxK,此时起码可以在时间为数组长度时吃完所有香蕉,即h=piles.length;
  • 此时,最小速度为1,最大速度为maxK,而区间[1,maxK]自然有序,所以可以考虑使用二分搜索算法求解合适的速度;
  • 合适的速度不唯一,只要大于等于minSpeed的速度均可完成目标,所以本题要求的最小速度其实,是通过二分搜索算法求解左边界问题

解题思路:

  • 确定最大香蕉堆的香蕉数量maxPile,以此作为最大速度,搜索区间为[1,maxPile];
  • 在此区间上进行二分搜索,以当前值mid作为速度speed进行尝试,确定最小速度;
    如何确定是否当前速度可实现目标:我们知道题目要求狒狒在一个小时内最多只吃speed个香蕉,如果当前香蕉堆的香蕉数量piles[i]>speed,则将当前堆剩余香蕉等到下一个小时再吃,所以可通过以下方式计算当前堆全部吃完的总耗时:piles[i]/speed+(piles[i]%speed==0?0:1)
class Solution {
    
    
    public int minEatingSpeed(int[] piles, int h) {
    
    
        int maxPile=getMaxPile(piles);
        int l=1,r=maxPile;  // 搜索区间[l,r]
        while (l<=r){
    
    
            int mid=l+(r-l)/2;
            if (restBanana(piles,h,mid)){
    
      // 可以吃完,说明速度大于等于最小速度
                r=mid-1;
            }else if (!restBanana(piles,h,mid)){
    
      // 吃不完,说明速度小于最小速度
                l=mid+1;
            }
        }
        if (l>maxPile){
    
    
            return -1;
        }
        return l;
    }

    // 以当前速度在h小时内是否可以吃完
    private boolean restBanana(int[] piles, int h, int speed) {
    
    
        int time=0;
        for (int pile:piles){
    
    
            time+=(pile/speed);
            time+=(pile%speed==0?0:1);
        }
        return time<=h;
    }

    // 确定最大香蕉堆数量,以此作为有可能的最大速度
    public int getMaxPile(int[] piles){
    
    
        int max=piles[0];
        for (int i=1;i<piles.length;i++){
    
    
            max=Math.max(max,piles[i]);
        }
        return max;
    }
}

代码框架,参考labuladong,https://labuladong.gitee.io/algo

猜你喜欢

转载自blog.csdn.net/qq_43665602/article/details/129856405