算法基础之二分查找

2023-09-22 15:30:13

原题链接

一 、二分查找中的mid+1和mid-1的问题

二分查找中的边界问题处理不好很容易导致死循环和计算错误的问题,以题目 数的范围为例。

  • 题目大意

​ 二分查找重复数第一次出现的位置和最后一次出现的位置。

  • 数学含义

​ 第一次位置即 找到 一个长度最大的 >=X 区间的 左边界

​ 最后一次位置即 找到一个长度最大的 >= X 区间的右边界

注意 找的目标是左边界或者右边界 不是找整个区间

  • 图形示意

    L=左边界 R=有边界 M=中间值(所选比较的数)T=目标位置

    M的意义在于通过缩小区间 找到位置T

image-20230905221643547

  • 重要结论

    重要的一步是将M的值传递出去时候,即 L=M 或者R = M,即把目标范围的左或右边界设置为M。

    因为不管是M+1或者是M-1 ,指针都会移动。直接赋值M的时候,指针可能不会移动,就会造成死循环。

二、具体代码分析

找第一次出现的位置

  • 循环结束的条件 L==R,最后一次循环时 L+1==R,即搜索的目标范围内有两个元素。
  • M 为L+R的下取整,有可能取到L,不可能取到R,但是赋值是赋给R,即R=L,最终条件 L==R 会结束循环。
int l = 0, r = n - 1;
while (l < r) {
    int mid = l + r >> 1;
    if (a[mid] < x)  l = mid + 1;
    else    r = mid;
}

找最后一次出现的位置

  • 方法一 以【0,n-1】闭区间,最小搜索范围是2个元素,取右边界

    因为M的值是赋给L的,即L=M,所以M不能取到左边界,所以要向上取整。

所谓的闭区间 ,个人理解最后一次循环时只有两个元素,左右指针在这两个数之间移动。

image-20230905224331820

int l = 0, r = n - 1;
while (l < r) {
    int mid = l + r + 1 >> 1;
    if (a[mid] <= x)  l = mid;
    else    r = mid-1;
}
  • 方法二 以【0,n) 左闭右开区间 ,最小搜索范围是3个元素,取中间元素

    所谓左闭右开区间,个人理解需要加上循环上的判断控制,才能形成开区间效果,使得最后一次循环时只有三个元素,左右指针在第一、第二个数直接移动。

    由于l +1 < r 保证了最小搜索范围是3个元素,所以 l + r >> 1 时M会取到L和R中间的数,不会取L或者R,主要保证不能取到L。

    image-20230905225702420

int l = 0, r = n ;
while (l +1 < r) {
    int mid = l + r  >> 1;
    if (a[mid] <= x)  l = mid;
    else    r = mid; // 想想此处可不可以写成 r=mid-1
}

第一次位置 左闭区间0开始

int l = 0, r = n - 1;
while (l < r) {
    int mid = l + r >> 1;
    if (a[mid] < x)  l = mid + 1;
    else    r = mid;
}
  • 方法三 以(-1,n)左开右开区间

左开是为了求第一次出现位置,同样保证最后一次循环是3个元素, l + r >> 1 时M会取到L和R中间的数,不会取L或者R,主要保证不能取到R,因为找第一次位置主要是将M赋值给R。

第一次位置

int l = -1, r = n - 1;
while (l+1 < r) {
    int mid = l + r >> 1;
    if (a[mid] < x)  l = mid ;
    else    r = mid;
}

最后一次位置

while (l+1 < r) {
    int mid = l + r >> 1;
    if (a[mid] <= x)  l = mid ;
    else    r = mid;
}

完整代码

需要注意最后输出

第一次坐标 的二分的边界定为 左边为=X 则所求为R

最后一次坐标的二分边界定位 左边为<=X 右边>X 则所求为L

#include<iostream>
using namespace std;
const int N=1e5+5;
int n,m,q[N];
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=0;i<n;i++) scanf("%d",&q[i]);
    while(m--)
    {
        int k;scanf("%d",&k);
        //寻找第一个等于K的坐标 我这边让二分的边界定为 左边为<5 右边>=5 则所求为r
        int l=-1,r=n;
        while(l+1<r)//当l与r没有相接的时候,求边界
        {
            int mid=l+r>>1;
            //下面找第一个>=5的坐标
            if(q[mid]>=k) r=mid;
            else l=mid;
        }
        //此时得到的r是第一个>=5的坐标
        if(q[r]!=k) {
            printf("-1 -1\n");
                        continue;

        }
        int ll = r,rr = n;
       while(ll+1<rr)
        {

            int mid=ll+rr>>1;
            if(q[mid]<=k) ll=mid;
            else rr=mid;
        }
       printf("%d %d\n", r, ll);

    }

}

三、个人心得体会

开区间 其实就是 最小搜索范围是3元素+左右新增点 的方式。这样就避免了L=M或者R=M 的死循环问题。

更多推荐

spring 拦截器

Spring拦截器是在处理请求的过程中,可以在特定的时机对请求进行一些处理,比如记录日志、进行权限校验、统计请求时间等。实现步骤:创建一个拦截器类,实现HandlerInterceptor接口,并重写其方法。在Spring配置文件中添加拦截器配置,可以配置拦截的URL,也可以对所有URL进行拦截。在拦截器的方法中编写拦

API(十)时间相关的SDK

一时间相关的SDK①时间记录的必要性1、'案发'现场的时间点2、通过时间判断'性能'3、时间的'不准确'性,日志'落盘'时间-->'缓冲区'导致延迟②使用哪些日期和时间的函数1、lua标准'时间'函数,函数'os.time'、'os.date'和'os.difftime'提供了所有日期和时间2、在openresty的世

vue3硅谷甄选01 | 使用vite创建vue3项目及项目的配置 环境准备 ESLint配置 prettier配置 husky配置 项目集成

文章目录使用vite创建vue3项目及项目的配置1.环境准备2.项目配置ESLint校验代码工具配置-js代码检测工具1.安装ESLint到开发环境devDependencies2.生成配置文件:`.eslint.cjs`**3.安装vue3环境代码校验插件**4.修改.eslintrc.cjs配置文件5.生成ESLi

接口自动化测试框架搭建全部过程

思想:1、基本目录的搭建report:静态输出目录(报告或者日志)data:静态输入目录(可以存放Excel数据,被读取的一些数据)utils:实用方法层(这里存放的是项目的公共方法,一般拿到别的项目可以直接使用,列如:读取Excel中的数据,连接数据库,)apis:接口请求层(这里封装的方法一般都是和项目有关系,列如

MySQL 权限分配

有时候,您需要查看某个用户被授予的权限以便复核。MySQL允许您使用SHOWGRANTS语句来显示分配给用户帐户或角色的权限。MySQLSHOWGRANTS语句介绍以下是SHOWGRANTS语句的基本语法:SHOWGRANTS[FOR{user|role}[USINGrole[,role]...]]在这个语法中:首先,

记录一次DLL分析实战

记录一次DLL分析实战1.VT查看分析报告2.判断文件是否加壳3.查看导入函数4.查看是否有任何其他文件或基于主机的迹象5.使用工具IDAPro进行字符串分析1.VT查看分析报告virustotal全绿,没有报毒:可以看到这个dll是32位的:下面可以看它调用的其他dll:以及它对外提供的函数接口:其中RunCmd很可

Redis简介

1.Nosql作用:应对基于海量用户和海量数据前提下的数据处理问题。​常见Nosql数据库:​RedismemcacheHBaseMongoDB​特征:可扩容,可伸缩,大数据量下高性能,灵活的数据模型,高可用2.Redis特征:1.数据间没有必然的关联关系2.内部采用单线程机制进行工作3.高性能。官方提供测试数据,50

OceanBase杨传辉传递亚运火炬:国产数据库为“智能亚运”提供稳稳支持

9月14日,亚运火炬传递到了浙江台州,OceanBase的CTO杨传辉作为火炬手交接了第89棒火炬。2010年,杨传辉作为创始成员之一参与自研原生分布式数据库OceanBase。十年磨一剑,国产数据库OceanBase交出了一张优秀的成绩单:连续10年稳定支撑双11,承受住了世界级的流量洪峰和稳定性考验;刷新过TPC-

go学习-GMP模型

GMP好理解还是GPM好理解?按照上述图,从上往下,GPM更适合理解GMP模型:Go语言运行时系统中的Goroutine、用于管理Goroutine调度的GoScheduler(P)、机器可用的逻辑处理器数量(M)。理解GPMG每个Goroutine是一个轻量级“线程”,称之为“协程”,可由Go运行时系统并发执行G与P

2023 Google 开发者大会:将大型语言模型部署到你的手机

在2022年末,不到半年时间,各家大语言模型的发展如雨后春笋,截至2023年9月,全球总共有接近100个大语言模型,可谓是百花齐放显而易见,大语言模型凭借出色的AI对话能力,已经逐渐深入各个行业2023Google开发者大会带来了AI专题,Google技术推广工程师魏巍提出“将大语言模型部署到个人终端”,关于这点,在外

[NLP] LLM---<训练中文LLama2(三)>对LLama2进行中文预料预训练

预训练预训练部分可以为两个阶段:第一阶段:冻结transformer参数,仅训练embedding,在尽量不干扰原模型的情况下适配新增的中文词向量。第二阶段:使用LoRA技术,为模型添加LoRA权重(adapter),训练embedding的同时也更新LoRA参数。第一阶段预训练由于第一阶段预训练会冻结transfor

热文推荐