基本的二分查找、寻找第一个和最后一个数的二分查找

二分查找

  • 1 二分查找的框架
  • 2 寻找一个数(基本的二分搜索)
  • 3 寻找左侧边界的二分搜索
  • 4 寻找右侧边界的二分查找
  • 5 合并

二分查找场景:有序数组寻找一个数、寻找左侧边界(有序数组第一个等目标数的下标)、寻找右侧边界(有序数组最后一个等于目标数的下标)

1 二分查找的框架

int binarySearch(int *nums,int numsSize,int target)
{int left=0,rigth = ...;while(...){int mid = left +(rigth-left)/2;if(nums[mid] == target){...}else if (nums[mid]<target){left = ...;}else if (nums[mid]>target){rigth = ...;}}return ...
}

使用else if是为了把所有情况写清楚,这样可以清楚的展现所有细节。本文都使用else if,旨在说清楚,可自行简化。

其中…标记的部分,就是可能出现细节问题的地方,当你见到一个二分查找的代码时,首先要注意这几个地方。

2 寻找一个数(基本的二分搜索)

这个场景是最简单的,即搜索一个数,如果存在,返回其索引,否则返回-1。

int binarySearch(int *nums,int numsSize,int target)
{int left=0;int right = numsSize-1; //注意while(left<=rigth)  //注意{int mid = left +(right-left)/2;if(nums[mid] == target)return mid;else if(nums[mid]<target){left = mid+1;   //注意}else if(nums[mid]>target){right = mid+1;  //注意}}return -1;
}
  • 为什么while的循环条件中是<=,而不是<?
    答:因为初始化right的赋值是numsSize-1,即最后一个元素的索引,而不是numsSize,这二者可能出现在不同功能的二分查找中,区别是:前者相当于两端都闭区间[left,right],后者相当于左闭右开区间[left,right),因为索引大小为numsSize是越界的。
    我们这个算法中使用的是[left,right]两端都闭的区间,这个区间就是每次进行搜索的区间,那while循环什么时候应该终止?搜索区间为空的时候应该终止,意味着你没的找了。

while(left<=right)终止条件是left==right+1,写成区间的形式是[ right+1,right],这个时候搜索区间为空,直接返回-1.

while(left<right)的终止条件是left==right,写成区间的形式是[right,right],者时候区间非空,还有一个数nums[right],如果此时while循环停止了,就漏掉一个数,如果这个时候返回-1就可能出现错误。

  • 为什么left=mid+1,right=mid-1?
    本算法的搜索区间两端都是闭的,即[left,right]。那么当我们发现索引mid不是要找的target时,如果确定下一步的搜索区间呢?
    当然是去搜索[left,mid+1]或者[mid+1,right],因为mid已经搜索过了,应该从搜索区间去除。
  • 此算法有什么缺陷?
    比如说你有有序数组nums=[1,2,2,2,3],此算法返回的索引是2,但是如果我想得到target的左侧边界,即索引1,或者想得到target的右侧边界,即索引3,这样的话,此算法是无法处理的。

3 寻找左侧边界的二分搜索

查找左侧边界的数,即在一个有序数组中,找到第一个等于target的下标。比如数组int nums[]={5,7,7,8,8,8,10},target=8,第一个等于8的下标是3,第一个大于等于8的数组下标也是3。即找到第一个等于target的数等价于 找第一个大于等于targte的数的下标,然后我们判断该下标所对应的数是否是target。

直接看代码:

/* 二分查找左侧边界 */
int lower_bound(int *nums,int numsSize,int target)
{int left=0,right=numsSize-1,ans=numsSize;while(left<=right){int mid = left+(right-left)/2;if(nums[mid]>=target){right = mid-1;ans = mid;}else {left = mid+1;}}return ans;
}
int main(void)
{int nums[]={5,7,7,8,8,8,10};int ret;//int p = high_bound(nums,7,8);//printf("%d \n",p);ret = lower_bound(nums,7,8);printf("%d \n",ret);return 0;
}

输出是3。
请添加图片描述
nums[mid]>=target时,说明有大于等于target的数了,我们需要更新ans来记录大于等于targte的数,right需要更新,然后继续往在[left,mid-1]区间找大于等于target的数,如果nums[mid]<target,则需要在[mid+1,right]区间找大于等于target的数,left下标所指向的值始终是小于等于target(如果全部数据全部大于target,left将不会变化),所以,当结束时,ans所指向的值是[left,right]区间最后一个大于等于target的值,left下标所指向的值又始终是小于等于target,所以ans所指向的内容是第一个大于等于target的值。

4 寻找右侧边界的二分查找

寻找右侧边界的数,就是找第一个大于target的数,返回其下标减1,int nums[]={5,7,7,8,8,8,10},最后一个等于8的下标是5,第一个大于8的数是10,其下标是6,减去1是5,找target最后位置等价于找第一个大于target的下标减1,然后判断该位置上的数是否等于target。
具体代码为:

/* 二分查找右侧边界 */
int high_bound(int *nums,int numsSize,int target)
{int left=0,right=numsSize-1,ans=numsSize;while(left<=right){int mid = left+(right-left)/2;if(nums[mid]>target){right = mid-1;ans = mid;}else {left = mid+1;}}return ans;     
}
int main(void)
{int nums[]={5,7,7,8,8,8,10};int ret;//int p = high_bound(nums,7,8);//printf("%d \n",p);ret = high_bound(nums,7,8)-1;printf("%d \n",ret);return 0;
}

运行结果是5。
如果nums[mid]>target,有大于target的数了,ans就要去记录,right更新,right=mid-1 ,如果nums[mid]<=target,则left需要更新,left=mid-1,left指示的内容始终是小于等于target的(如果数据全部大于target,left不会变)。只要left<=right,就会一直缩小区间,当运行结束后,ans所指示的内容是最后一个大于target的数。

5 合并

我们添加一个参数,表示找第一个等于target的数,还是找最后一个等于target的数。

int binarySearch(int* nums, int numsSize, int target, bool lower) {int left = 0, right = numsSize - 1, ans = numsSize;while (left <= right) {int mid = (left + right) / 2;if (nums[mid] > target || (lower && nums[mid] >= target)) {right = mid - 1;ans = mid;} else {left = mid + 1;}}return ans;
}

当lower为真时,表示找第一个大于等于target的数,当lowe为假时,表示找第一个大于target的数,返回之后,检查和上面代码一样。

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

示例代码:


/* 
在一个升序数组中,找第一个等于target的数组,即找第一个大于等于target的数,返回其下标,最后判断
是否等于targettarget。
找最后一个等于target的数组,即找第一个大于target的数,返回其下标减1,最后判断该下标对应的数是否等于target。
*/
int binarySearch(int *nums,int numsSize,int target,bool lower)
{int left=0,right=numsSize-1,ans=numsSize;while(left<=right){int mid=left+(right-left)/2;if(nums[mid]>target || (lower && nums[mid]>=target)){right=mid-1;ans = mid;}else{left=mid+1;}}return ans;
}
/*** Note: The returned array must be malloced, assume caller calls free().*/
int* searchRange(int* nums, int numsSize, int target, int* returnSize){int leftIdx = binarySearch(nums,numsSize,target,true);int rightIdx = binarySearch(nums,numsSize,target,false)-1;int *ret = (int *)malloc(sizeof(int)*2);*returnSize=2;if(leftIdx<=rightIdx && nums[leftIdx]==target && nums[rightIdx]==target){ret[0]=leftIdx;ret[1]=rightIdx;return ret;}ret[0]=-1;ret[1]=-1;return ret;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/379666.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PostgreSQL 中的递归查询 与oracle 的比较

PostgreSQL 中的递归查询&#xff0c;2种方法&#xff1a; 1、用with decursive WITH RECURSIVE d AS (SELECT d1.id,d1.parent_id,d1.caption FROM course_types d1 where d1.dr 0 and d1.idtypeId union ALL SELECT d2.id,d2.parent_id,d2.caption FROM course_types d2, d …

教你如何玩转GitHub

使用GitHub ①目的&#xff1a;借助GitHub托管项目代码 基本概念&#xff1a; ①仓库(Repository)&#xff1a; 用来存放项目代码&#xff0c;每个项目对应一个仓库&#xff0c;多个开源项目对应多个仓库 ②收藏(Star)&#xff1a; 收藏项目&#xff0c;方便下次查看 ③…

Java SecurityManager checkDelete()方法与示例

SecurityManager类的checkDelete()方法 (SecurityManager Class checkDelete() method) checkDelete() method is available in java.lang package. checkDelete()方法在java.lang包中可用。 checkDelete() method calls checkPermission with FilePermission(filename,"d…

jQuery中的treeview插件

jQuery做树状结构真的很简单,下面做一个最简单的示例: 在html文件中引用: <link rel"stylesheet" href"../jquery.treeview.css" /> <link rel"stylesheet" href"../red-treeview.css" /> <link rel"styles…

Linux内核设计与实现---中断和中断处理程序

中断和中断处理程序1 中断异常2 中断处理程序上半部与下半部的对比3 注册中断处理程序释放中断处理程序4 编写中断处理程序重入和中断处理程序共享的中断处理程序中断处理程序实例5 中断上下文6 中断处理机制的实现7 中断控制禁止和激活中断禁止指定中断线中断系统的状态8 总结…

asp.net中的窗体身份验证(最简单篇)

在创建网站中&#xff0c;常常会使用到身份验证。asp.net中内置了几种身份验证的方式&#xff0c;如Windows、Froms、Passport等。这几种身份验证的方式各有不同。一般来说&#xff0c;网站的身份验证方式都会经过以下几个步骤&#xff1a; 1、输入用户名和密码&#xff0c;单击…

bat文件调用dos命令 (dos淘金)

ECHO命令是大家都熟悉的DOS批处理命令的一条子命令&#xff0c;但它的一些功能和用法也许你并不是全都知道&#xff0c;不信你瞧&#xff1a; 1&#xff0e; 作为控制批处理命令在执行时是否显示命令行自身的开关 格式&#xff1a;ECHO [ON|OFF] 如果想关闭“ECHO OFF”命令…

response细节点

一、 1&#xff09;、response获得的流不需要手动关闭&#xff0c;Tomcat容器会帮你自动关闭 2&#xff09;、getWriter和getOutputStream不能同时调用 //error package com.itheima.content;import java.io.IOException; import javax.servlet.ServletException; import java…

Java RandomAccessFile writeBytes()方法与示例

RandomAccessFile类writeBytes()方法 (RandomAccessFile Class writeBytes() method) writeBytes() method is available in java.io package. writeBytes()方法在java.io包中可用。 writeBytes() method is used to write the sequence of bytes (i.e. string) to the file. E…

linux内核设计与实现---下半部和推后执行的工作

下半部和推后执行的工作1 下半部为什么要用下半部下半部的环境内核定时器2 软中断软中断的实现软中断处理程序执行软中断使用软中断3 tasklettasklet的实现使用taskletksoftirqd4 工作队列工作队列的实现工作、工作队列和工作者线程之间的关系使用工作队列5 下半部机制的选择6 …

Jquery对复选框的操作

<from> 你的爱好是?<br/> <input type"checkbox" name"items" value"篮球" />篮球 <input type"checkbox" name"items" value"乒乓球" />乒乓球 <input type"checkbox" na…

HttpServletRequest(request的一些API)

一、request的运行流程 首先&#xff0c;自己写一个web工程&#xff0c;也就是建一个工程&#xff1b;当把该web工程发布到Tomcat服务器当中&#xff0c;可以让外界访问&#xff0c;这就成了一个web应用。 在客户端输入一个网站&#xff0c;是web应用资源的地址URL&#xff0c…

DCI:James O. Coplien和Trygve Reenskau提出的新架构方法

http://www.infoq.com/cn/news/2009/05/dci-coplien-reenskau 转载于:https://www.cnblogs.com/yelinpalace/archive/2009/06/13/1502573.html

Java ObjectStreamField getOffset()方法与示例

ObjectStreamField类的getOffset()方法 (ObjectStreamField Class getOffset() method) getOffset() method is available in java.io package. getOffset()方法在java.io包中可用。 getOffset() method is used to get the offset of this ObjectStreamField field. getOffse…

Mac VSCode配置C语言环境(可以调试)

Mac VSCode配置C语言环境c_cpp_properties.jsontasks.jsonlaunch.json新建一个文件夹&#xff0c;用vscode&#xff0c;然后再新建一个test.c文件。 #include <stdio.h>int main(void) {int a1,b1;int cab;printf("%d\n",c);return 0; }这篇文章说怎么配置c_c…

XmlPullParserException

今天在android的开发中约到一个问题 使用Ksoap2 访问 WebService 抛出 XmlPullParserException 异常。 在网上淘了一下这个问题 http://www.eoeandroid.com/thread-70527-1-1.html 不能解决我的问题&#xff0c;求解转载于:https://www.cnblogs.com/pengqinping/archive/2012/0…

vShpere Client在win 7 RC下和2008下 无法正常连接esx主机之解决办法

vShpere Client在win 7 RC下和2008下 无法正常连接esx主机之解决办法 在win7下和2008下打开client后连接esx主机会出现2个错误提示, 第一个是 第二个是 然后就连接失败了,开始以为是CC的esx主机安装有问题,后来找了找,借助了强大google工具,终于找到解决办法.解决办法如下: 1.从…

tooctalstring_Java Integer类toOctalString()方法的示例

tooctalstring整数类toOctalString()方法 (Integer class toOctalString() method) toOctalString() method is available in java.lang package. toOctalString()方法在java.lang包中可用。 toOctalString() method is used to represent an octal string of the given parame…

localhost与127.0.0.1之间的关系更改

其实localhost的默认IP地址为127.0.0.1&#xff0c;因为这是一种映射关系。 更改步骤如下&#xff1a; C:\Windows\System32\drivers\etc 下的hosts 打开hosts可以看到 更改即可

基于Hash表的排序--C语言

我们知道&#xff0c;C语言里面是没有hash表的&#xff0c;但是我们可以用一个结构体表示&#xff0c;对结构体排序&#xff0c;我们可以用qsort排序。 下面我们用一个LeedCode上面的一道题目讲解。 347. 前 K 个高频元素 这个题目是让我们求解前k个高频元素&#xff0c;求解思…