首页 > 编程语言 >算法基础之二分查找

算法基础之二分查找

时间:2023-09-23 15:33:59浏览次数:49  
标签:二分 边界 int 位置 mid while 算法 查找 else

原题链接

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

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

  • 题目大意

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

  • 数学含义

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

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

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

  • 图形示意

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

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

    image-20230905221643547image-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-20230905224331820image-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-20230905225702420image-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 右边>=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 的死循环问题。

原题链接

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

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

  • 题目大意

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

  • 数学含义

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

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

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

  • 图形示意

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

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

    image-20230905221643547image-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-20230905224331820image-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-20230905225702420image-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 右边>=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 的死循环问题。

标签:二分,边界,int,位置,mid,while,算法,查找,else
From: https://www.cnblogs.com/zhaodong462502/p/17724451.html

相关文章

  • 深度学习算法中的参数共享(Parameter Sharing)
    引言在深度学习算法中,参数共享(ParameterSharing)是一种重要的技术,它通过共享模型的参数来减少模型的复杂度,并提升模型的性能和泛化能力。本文将介绍参数共享的概念、原理以及在深度学习算法中的应用。参数共享的概念参数共享指的是在模型的不同部分使用相同的参数。在传统的机器学......
  • 基本查找 / 顺序查找
    //基本查找/顺序查找核心:从0索引开始挨个查找//例:查询元素number是否存在int[]arr={131,127,147,81,103,23,7,79};System.out.println("请输入要查找的数:");Scannersc=newScanner(System.in);StringnumberString=sc.nextLine();intnumber=Integer.parseI......
  • 算法训练day8 LeetCode 344
    算法训练day8:LeetCode344.541.151.剑指offer05.58.344.反转字符串题目344.反转字符串-力扣(LeetCode)题解代码随想录(programmercarl.com)classSolution{public:voidreverseString(vector<char>&s){for(inti=0,j=s.size()-1;i......
  • 算法题——定义一个方法自己实现 toBinaryString 方法的效果,将一个十进制整数转成字符
    用除基取余法,不断地除以基数(几进制,基数就是几)得到余数,直到商为0,再将余数倒着拼起来即可。privatestaticStringtoBinaryString(intnumber){StringBuildersb=newStringBuilder();while(true){if(number==0)break;intyushu=num......
  • 常用算法模版
    常用算法模版今天学会在https://godbolt.org/看汇编了。顺便卡了下常数,以及简单的(不是)压行。快读signedread(){signednum=0,flag=1;charch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')flag=-1;for(;isdigit(ch);ch=g......
  • 算法题——实现类似parseInt的方法
    Scannersc=newScanner(System.in);Stringstr="";while(true){System.out.println("请输入");Stringstr1=sc.nextLine();if(str1.length()<1||str1.length()>10||str1.charAt(0)=='0'){System.out.......
  • 【算法】哈希表
    1哈希表理论基础1.1哈希表哈希表是根据关键码的值而直接进行访问的数据结构。一般哈希表都是用来快速判断一个元素是否出现集合里。1.2哈希函数哈希函数如下图所示,通过hashCode把名字转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值。如果ha......
  • 【算法】字符串
    1反转字符串题目:编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。不要给另外的数组分配额外的空间,你必须**原地修改输入数组**、使用O(1)的额外空间解决这一问题。1.双指针classSolution:defreverseString(self,s:List[str])......
  • 网络拥塞控制算法总结-PolyCC
    字节跳动在SIGCOMM'23以Poster形式提交了一篇论文《PolyCC:Poly-AlgorithmicCongestionControl》,试图将各种拥塞控制算法整合到一个统一的框架里。其理由是近40年来各种渠道发布的各种拥塞控制算法,没有一种算法能解决所有网络场景(不同的应用,不同的流量模型等)。 如上图,PolyCC......
  • 【算法】链表
    1链表理论基础链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。链表的入口节点称为链表的头结点也就是head。链表中的节点在内存中不是连续分布的,而是散乱分布在内......