二分搜索算法 (binary search)

二分搜索算法

二分的本质并不是单调性,但是具有单调性一定可以使用二分,可以使用二分不一定具有单调性。二分的要点是"边界问题"

二分算法看似很容易实现,但是对于处理二分的边界上的细节时却十分复杂。

所以使用二分算法之前需要根据实际问题进行建模,根据不同的性质设置判断条件,划分红蓝区域,最终定位边界

请看下图:

找出蓝色的边界的数字和红色边界的数字。

在这里插入图片描述

可以将图中的一串数字根据颜色的不同,而划分成红蓝两个区域,可以使用通过二分法来确定红蓝区域的边界

在这里插入图片描述

两头分别设立两个指针,lrl 是从左相向右扫描蓝色的数字,r 是从右向左扫描红色的部分。

根据 lr 可以确认mid的位置:
在这里插入图片描述

使用二分法时,mid指向的位置是蓝色的边界位置,并且,根据以上数字的颜色不同的性质,我们可以知道,在mid指向的位置开始,左面的都是蓝色的,右面的都是红色的。

想要使用二分找到蓝色的边界,就需要不断的挪动 l 、 rmid 这三个指针所指向的位置,在 lmid 还有 r 所指向的位置相同的时候,就代表找到了这个边界。

要找到左边部分也就是蓝色部分的边界,就需要将边界划分为这样的两个部分:
[ l , mid][ mid+1, r ] 并且需要这样处理 mid: mid = (l + r)/ 2

同理,要找到红色部分的边界时就需要将边界改变 : [ l, mid - 1 ][ mid, r ] ,因为红色部分的边界是蓝色部分边界的下一个位置,所以mid需要这样处理:mid = (l + r + 1) / 2 ,加一个 1 就正好可以将mid指向蓝色边界的下一个位置也就是红色边界的位置。
在这里插入图片描述

根据以上的操作就可以找到红蓝颜色的边界。

在这里插入图片描述

整数二分

二分法查找需要注意边界问题,根据边界的实际情况来选择使用那种模板。其实就是看mid属于左边还是右边。

模板一:

// 区间[l, r]被划分成 [l, mid] 和 [mid+1, r]时使用
int bsearch_1(int l, int r) {
    while (l < r) {
        int mid = l + r >> 1;
        if (check(mid))  // check() 判断 mid是否满足性质
            r = mid; 
        else
            l = mid + 1;
    }
    return l;
}

模板二:

// 区间[l, r]被划分成 [l, mid-1] 和 [mid, r]时使用
int bsearch_2(int l, int r) {
    while (l < r) {
        int mid = l + r + 1 >> 1;   // 注意
        if (check(mid))  // check() 判断 mid是否满足性质
            l = mid;
        else
            r = mid - 1;
    }
    return l;
}

经典问题:

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

如果给定这个数组 [5,7,7,8,8,10] ,而 target 是 8

根据这个数组可以将问题分成两个部分,<= 8 的部分,和 >= 8 的部分,而这个两个部分的边界值就是我们要寻找的答案

先来找到 <= 8 的部分的边界,8 出现的起始位置:

在这里插入图片描述

<= 8 的部分是蓝色的,8 包含在蓝色的范围内。所以需要采用模板一,区间[l, r]被划分成 [l, mid][mid+1, r] 时的情况。

在看 >= 8 的部分,也就是 8 的结束位置:

在这里插入图片描述

也就是红色部分,此时 8 属于红色部分 ,要找右边部分的边界需要使用模板二,也就是区间[l, r]被划分成 [l, mid-1][mid, r] 时的情况。

可以将 mid 指向的数字理解为我们要寻找的答案。

这样,通过两次二分法查找,一次查找左边界,一次查找右边界,就可以找到答案。

class Solution {
    public int[] searchRange(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return new int[]{-1, -1};
        }            
        int l = 0, r = nums.length - 1;
        int[] res = new int[2];
        while (l < r) {
            int mid = l + r >> 1;
            if (target <= nums[mid]) {
                r = mid;
            } else {
                l = mid + 1;
            }
        }
        if (nums[l] != target) {
            res[0] = -1;
            res[1] = -1;
            return res;
        }
        res[0] = l;
        l = 0;
        r = nums.length - 1;
        while (l < r) {
            int mid = l + r + 1 >> 1;
            if (target >= nums[mid]) {
                l = mid;
            }else {
                r = mid - 1;
            }
        }
        res[1] = l;
        return res;      
    }
}

浮点数二分

对于面向浮点数二分的算法相较于整数二分更简单。

浮点数二分不需要考虑边界值的问题,因为浮点型数字在精度允许的情况下可以不断的二分下去。

浮点数二分模板:

public double bsearch(double n) {
    final double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}

例题:

acwing 790. 数的三次方根

  • 给定一个浮点数 n,求它的三次方根
  • −10000≤n≤10000结果保留 6 位小数
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        double n = sc.nextDouble();
        double l = -10000, r = 10000;
        while (r - l > 1e-8) {
            double mid = (l + r)/2;
            if(mid * mid * mid > n) r = mid;
            else l = mid;
        }
        System.out.printf("%.6f", l);
    }
}
  • 11
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神烦狗闯入了你的博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值