二分查找

  • 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;
}

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

  1. 二分查找之第一个大于小于等于 target 的值

    目录 欢迎浏览作者的[GitHub](https://github.com/loversgzl/Learning) 二分查找概述 1.查找某个值 target 2.查找第一个大于(大于等于) targ ...

  2. LeetCode4寻找两个有序数组的中位数(二分查找+分治)

    目录 1.题目 2.分析 3.代码 二分查找:绝大多数二分查找问题利用的是单调性,也有一些例外)或者题目本身蕴含的可以逐渐缩小问题规模的特性解决问题.时间复杂度log级. 分治法的设计思想是:将一个难 ...

  3. LeetCode-二分查找-278. 第一个错误的版本

    278. 第一个错误的版本 思路:二分查找法 // The API isBadVersion is defined for you. // bool isBadVersion(int version) ...

  4. list 查找_五千字长文带你学习 二分查找算法

    点击上方"与你一起学算法",选择"星标"公众号 重磅干货,第一时间送达 二分查找的思想 提及二分查找算法,我想大部分人都不陌生,就算不是学计算机的,基本上也都使 ...

  5. LeetCode--34.在排序数组中查找元素第一个和最后一个位置(二分法)

    在排序数组中查找元素第一个和最后一个位置(C++) 1. 题目描述 2. 题目分析 3. C++语言实现 1. 题目描述 难度:中等 2. 题目分析 看到题目我们需要知道以下几点: 数组是有序的 数组 ...

  6. AcWing 算法基础课第一节基础算法1排序、二分

    1.该系列为ACWing中算法基础课,已购买正版,课程作者为yxc 2.y总培训真的是业界良心,大家有时间可以报一下 3.为啥写在这儿,问就是oneNote的内存不够了QAQ ACwing C++ 算 ...

  7. 面试题之在字符串中查找出第一个只出现一次的字符的位置

    样例:比如"abcdabc",第一个只出现一次的字符为d,位置为3 解决方案1:O(n*n)的复杂度 遍历字符串中的每个字符,然后用该字符在字符串中进行查找,如果没有找到和当前字符 ...

  8. 信息学奥赛一本通 提高篇 第一部分 基础算法 第2章 二分与三分

    信息学奥赛一本通 提高篇 提高版 第一部分 基础算法 第2章 二分与三分 信息学奥赛一本通 提高篇 提高版 第一部分 基础算法 第2章 二分与三分_mrcrack的博客-CSDN博客_信息学奥赛一本通 ...

  9. python 二分查找_LeetCode基础算法题第120篇:二分查找算法

    技术提高是一个循序渐进的过程,所以我讲的leetcode算法题从最简单的level开始写的,然后> 到中级难度,最后到hard难度全部完.目前我选择C语言,Python和Java作为实现语言,因 ...

最新文章

  1. Python入门基础教程 Working with Python – Introductory Level
  2. 计算机无法用telnet,为何我的电脑cmd没法使用telnet命令?
  3. Spring--IoC(1)
  4. 如何在不安装 Microsoft Office 的情况下生成 Excel 文件?
  5. ansible执行mysql命令,Ansible常用命令(ad-hoc 命令)
  6. 《Python 黑科技》代理ip奇技淫巧
  7. 【并查集】Union Find
  8. 09-解决服务器被黑上不了网的问题
  9. 2020 年百度之星·程序设计大赛 - 初赛一 Civilization BFS广搜
  10. 最新CleanMyMac支持MacOS 12.x
  11. 中国联通517活动-沃福卡-技术分解实现方案
  12. PS制作gif动图教程
  13. 图片批量合成PDF方法
  14. 【Python】pandas的read_csv参数简略概括(header,path),DataFrame的返回值describe,plot,head
  15. 含泪整理最优质时间轴网页特效素材,你想要的这里都有
  16. 聊一聊C语言位域/位段
  17. 程序员恭喜了!11月起逼自己拿下这个证,年薪68万起!
  18. 客户关系管理项目——用户登录模块设计
  19. 计算机学院元旦晚会对联,元旦的对联
  20. Spark源码走读概述

热门文章

  1. js操作json方法总结
  2. JS中apply和call的应用和区别
  3. layui的checkbox示例
  4. Windows PowerShell Cookbook
  5. ##API(二)————包装类
  6. 迭代加深搜索 C++解题报告 :[SCOI2005]骑士精神
  7. Java基础知识学习04-自定义类、ArrayList集合
  8. css中position初解
  9. GCDAynscSocket简单使用-客户端
  10. 初步体验数据驱动之美---TreeView