总结:在一个有上下界的区间里搜索一个整数L69,L287,L374,L875,L1300

本文深入探讨二分查找算法的多种应用场景,包括寻找特定数值、优化效率问题、解决复杂数学问题等。通过实例解析,如寻找数组中的特定元素、求解平方根、预测香蕉消耗速率、猜数字游戏、寻找重复数、调整数组和等,详细阐述二分查找的实现技巧与注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.寻找当前第一个>=H的位置

class Solution {
    public int find(int[] nums, int H) {
        int left = 0, right = nums.length;//注意可以取到len
        while(left < right){
            int mid = (left + right)>>>1;
            if(nums[mid] >= H){//此处为>=
                right = mid;
            }else{
                left = mid + 1;
            }
        }
        return left;
    }
}

2.寻找第一个>H的数

class Solution {
    public int find(int[] nums, int H) {
        int left = 0, right = nums.length;//注意可以取到len
        while(left < right){
            int mid = (left + right)>>>1;
            if(nums[mid] > H){//唯一变化的地方
                right = mid;
            }else{
                left = mid + 1;
            }
        }
        return left;
    }
}

L69

  1. x 的平方根 实现 int sqrt(int x) 函数。

计算并返回 x 的平方根,其中 x 是非负整数。

由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

示例 1:

输入: 4 输出: 2 示例 2:

输入: 8 输出: 2 说明: 8 的平方根是 2.82842…,
由于返回类型是整数,小数部分将被舍去

目前注意的是,mid为向上取整,主要在于left = mid,这种赋值语句,容易mid取值时,在只有两个数的时候,mid =left,始终成立,程序无法结束。
同时在书写的时候,注意,判断等号究竟和谁在一起,>=还是<=,依据就是等号和谁在一起是同一种情况,同时对应的left,right一般为一个等于mid ,一个等于mid+1,或者mid -1;

class Solution {
    public int mySqrt(int x) {
        if(x == 0) return 0;
        //注意考虑边界情况,有可能存在0,1的值,但是right不一定能取到1
        //为防止溢出,都用long
        long left = 0, right = x/2 +1;//其实可以直接取x-1;
        while(left < right){
            long mid = left + (right - left +1)/2;//注意右边界为mid,要加一,不然会超时
            //目标是寻找当前最接近的且小于x的值
            long res = mid * mid;
            if(res > x){
                right = mid - 1;//其实模板与寻找第一个大于x的位置相似,只是right = mid - 1;
            }else{
                left = mid;//这个也是不同的地方
            }
        }
        return (int)left;
    }
}

L875

  1. 爱吃香蕉的珂珂 珂珂喜欢吃香蕉。这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 H 小时后回来。

珂珂可以决定她吃香蕉的速度 K (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 K 根。如果这堆香蕉少于 K
根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。

珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。

返回她可以在 H 小时内吃掉所有香蕉的最小速度 K(K 为整数)。

示例 1:

输入: piles = [3,6,7,11], H = 8 输出: 4 示例 2:

输入: piles = [30,11,23,4,20], H = 5 输出: 30

class Solution {
    public int minEatingSpeed(int[] piles, int H) {
        //先确定速度的范围,二分查找
        if(piles.length == 0) return  -1;
        int left = 1;
        int right = 0;
        for(int pile : piles){
            right = Math.max(right, pile);
        }
        //速度与耗时是线性相关,但是是负相关,所以要变化一下
        //二分查找,实际是找最接近H,即小于等于的位置,与寻找第一个大于等于的类似
        while(left < right){
            int mid = (left + right )>>>1;
            if(judge(piles, mid) <= H){//唯一变化的地方就是>=变成<=
                right = mid;
            }else{
                left = mid + 1;
            }
        }
        return  left;
    }
    int judge(int[] nums, int val){
        int sum = 0;
        for(int num:nums){
            sum += (num + val - 1)/val;//注意要往上取整
            //这就是向上取整的表达式,前提得是num,val均为整数才行,小数则不满足了
        }
        return sum;
    }
}

L287

== 这个数据hot100 ==

  1. 寻找重复数 给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

示例 1:

输入: [1,3,4,2,2] 输出: 2 示例 2:

输入: [3,1,3,4,2] 输出: 3 说明:

不能更改原数组(假设数组是只读的)。 只能使用额外的 O(1) 的空间。 时间复杂度小于 O(n2) 。
数组中只有一个重复的数字,但它可能不止重复出现一次。

class Solution {
    public int findDuplicate(int[] nums) {
        int len = nums.length;
        if(len == 0) return -1;//不过题目是说一定存在的
        //利用二分法,非常规的二分法,数组虽然不是有序,但是一定是顺序分布,最多有一个重复的
        int left = 0, right = len - 1;

        while(left < right){//当left = right时,一定能找到,内心要先把数组进行排序
            //实质是对排序之后的数组的搜索
            int mid = (left + right) >>>1;//这样无符号右移,不会溢出
            int cnt = 0;
            //因为现在是乱序的,所以要整体遍历
            //遍历整个数组<= mid的个数
            for(int i = 0; i < len; i++){//注意数组仍然从0开始,不要写成1,只是left,right指的是具体值
                if(nums[i] <= mid)cnt++;
            }
            if(cnt > mid){//当前严格大于mid,说明1-mid之间一定有重复的
                right = mid;
            }else{
                left = mid + 1;
            }
        }
        return left;
    }
}

L374

  1. 猜数字大小 我们正在玩一个猜数字游戏。 游戏规则如下: 我从 1 到 n 选择一个数字。 你需要猜我选择了哪个数字。 每次你猜错了,我会告诉你这个数字是大了还是小了。 你调用一个预先定义好的接口 guess(int num),它会返回 3
    个可能的结果(-1,1 或 0):

-1 : 我的数字比较小 1 : 我的数字比较大 0 : 恭喜!你猜对了! 示例 :

输入: n = 10, pick = 6 输出: 6

class GuessGame {

    private static final int NUM = 6;

    int guess(int num) {
        if (num == NUM) {
            return 0;
        } else if (num < NUM) {
            return -1;
        }
        return 1;
    }
}
public class Solution extends GuessGame {
    public int guessNumber(int n) {
        int left = 1, right = n;
        while(left <right){
            int mid = (left + right)>>>1;//不加上1就是会超时,因为mid分在了右边
            //int mid= (left + right)/2;即使不溢出,也是超时,关键要+ 1
            int val = guess(mid);
            if(val == -1){//说明当前真实的数字比mid较小
                right = mid - 1;
            }else{
                left = mid;//mid也是有可能的
            }
        }
        return left;
    }
}

对比一下

public class Solution extends GuessGame {
    public int guessNumber(int n) {
        int left = 1, right = n;
        while(left <right){
            int mid = (left + right)>>>1;//对于这种即使不+1也是不会出错
            //如果加上1反而会超时!!!
            //int mid= (left + right)/2;
            int val = guess(mid);
            if(val == 1){//说明当前真实的数字比mid较大
                left = mid + 1;
            }else{
                right = mid;//mid也是有可能的
            }
        }
        return left;
    }
}

多放一种if分支

public class Solution extends GuessGame {
    public int guessNumber(int n) {
        int left = 1, right = n;
        while(left <right){
            int mid = (left + right + 1)>>>1;
            //int mid= (left + right)/2;
            int val = guess(mid);
            if(val == 1){//说明当前真实的数字比mid较大
                left = mid + 1;
            }else if(val == -1){
                right = mid - 1;
            }else return mid;//其实碰到概率还是很低的,不见得会时间短
        }
        return left;
    }
}

L1300

(前面总结的规律再一次体现)

  1. 转变数组后最接近目标值的数组和 给你一个整数数组 arr 和一个目标值 target ,请你返回一个整数 value ,使得将数组中所有大于 value 的值变成 value 后,数组的和最接近 target (最接近表示两者之差的绝对值最小)。

如果有多种使得和最接近 target 的方案,请你返回这些整数中的最小值。

请注意,答案不一定是 arr 中的数字。

示例 1:

输入:arr = [4,9,3], target = 10 输出:3 解释:当选择 value 为 3 时,数组会变成 [3, 3,
3],和为 9 ,这是最接近 target 的方案。 示例 2:

输入:arr = [2,3,5], target = 10 输出:5 示例 3:

输入:arr = [60864,25176,27249,21296,20204], target = 56803 输出:11361

提示:

1 <= arr.length <= 10^4 1 <= arr[i], target <= 10^5

在这里插入图片描述
此题在于,并不是严格单调的,当value如果过大的话,其sum值有可能是保持不变的,所以当等于target时,并不退出循环
同时有可能出现图中的情况,一般left - 1,所以最终二者再进行比较即可

class Solution {
    public int findBestValue(int[] arr, int target) {
        if(arr == null || arr.length == 0) return -1;
        int left = 0, right = target;
        //参考的value值越大,对应的总和就越大,线性相关
        //寻找第一个大于等于targe的值,常规做法
        while(left < right){
            int mid = (left + right)>>>1;
            if(calcu(arr, mid) >= target){
                right = mid;
            }else{
                left = mid + 1;
            }
        }
        //有可能是在left与left - 1之间,所以还需要再次判断
        //注意left,right表示的是最终的值,不是位置,不需要考虑数组越界
        int ans1 = calcu(arr, left - 1);
        int ans2 = calcu(arr, left);
        if(target - ans1 <= ans2 - target){//判断哪个更接近
            return left - 1;
        }else return left;
    }
    int calcu(int[] arr, int val){
        int sum = 0;
        for(int num:arr){
            sum += Math.min(val, num);
        }
        return sum;
    }
}

2.同样可以寻找小于target,同时也是最接近的数
与69题有区别

class Solution {
    public int findBestValue(int[] arr, int target) {
        if(arr == null || arr.length == 0) return -1;
        int left = 0, right = target;
        //参考的value值越大,对应的总和就越大,线性相关
        //寻找最后一个小于等于targe的值,常规做法
        //与69题的区别在于,此题是找最接近的,[2,3,5],10,输出:10,正确结果为5
        //所以即使当前满足calcu(arr, mid) = target,仍然还是要缩小,对应的calcul规则是不同的
        while(left < right){
            int mid = (left + right +1)>>>1;//由于left = mid,所以加1
            if(calcu(arr, mid) >= target){//这里为>=,与69题还是不同的
                right = mid - 1;//对应这里就要改变一下
            }else{
                left = mid;
            }
        }
        //有可能是在left与left + 1之间,所以还需要再次判断
        //注意left,right表示的是最终的值,不是位置,不需要考虑数组越界
        int ans1 = calcu(arr, left);
        int ans2 = calcu(arr, left + 1);
        if(target - ans1 <= ans2 - target){//判断哪个更接近
            return left;
        }else return left + 1;
    }
    int calcu(int[] arr, int val){
        int sum = 0;
        for(int num:arr){
            sum += Math.min(val, num);
        }
        return sum;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值