写在前面:博主是一位普普通通的19届双非软工在读生,平时最大的爱好就是听听歌,逛逛B站。博主很喜欢的一句话花开堪折直须折,莫待无花空折枝:博主的理解是头一次为人,就应该做自己想做的事,做自己不后悔的事,做自己以后不会留有遗憾的事,做自己觉得有意义的事,不浪费这大好的青春年华。博主写博客目的是记录所学到的知识并方便自己复习,在记录知识的同时获得部分浏览量,得到更多人的认可,满足小小的成就感,同时在写博客的途中结交更多志同道合的朋友,让自己在技术的路上并不孤单。

目录:
1.二分查找简介
2.基本二分搜索
        基本二分搜索简介
        LeetCode 69.x的平方根
        LeetCode 374.猜数字大小
        LeetCode 33.搜索旋转排序数组
3.寻找左侧边界的二分搜索
        寻找左侧边界的二分搜索简介
        LeetCode 278.第一个错误的版本
        LeetCode 162.寻找峰值
        LeetCode 153. 寻找旋转排序数组中的最小值
4.寻找右侧边界的二分查找
        寻找右侧边界的二分查找简介
        LeetCode34.在排序数组中查找元素的第一个和最后一个位置

1.二分查找

二分查找法(Binary Search)算法,也叫折半查找算法。二分查找针对的是一个有序(如果集合是无序的,我们可以总是在应用二分查找之前先对其进行排序。)的数据集合,查找思想有点类似于分治思想。每次都通过跟区间的中间元素对比,将带查找的区间缩小为之前的一半,知道找到要查找的元素,或者区间被缩小为0。二分查找是一种非常非常高效的查询算法,时间复杂度未O(logn)。

2.基本二分搜索

2.1基本二分搜索简介与代码模板

本分查找的最基础和最基本的形式。
查找条件可以在不与元素的两侧进行比较的情况下确定(或使用它周围的特定元素)。
不需要后处理,因为每一步中,你都在检查是否找到了元素。如果到达末尾,则知道未找到该元素。

int binarySearch(vector<int>& nums, int target){if(nums.size() == 0)return -1;int left = 0;right = nums.size() - 1;while(left <= right){int mid = left + (right - left) / 2;if(nums[mid] == target)return mid; else if(nums[mid] < target) left = mid + 1;elseright = mid - 1; }return -1;
}

可能在基本二分搜索会遇到如下问题:

2.为什么 while 循环的条件中是 <=,而不是 <?

答:因为初始化 right 的赋值是 nums.length - 1,即最后一个元素的索引,而不是 nums.length。
这二者可能出现在不同功能的二分查找中,区别是:前者相当于两端都闭区间 [left, right],后者相当于左闭右开区间 [left, right),因为索引大小为 nums.length 是越界的。
我们这个算法中使用的是前者 [left, right] 两端都闭的区间。这个区间其实就是每次进行搜索的区间。
什么时候应该停止搜索呢?当然,找到了目标值的时候可以终止:

if(nums[mid] == target)return mid;

但如果没找到,就需要 while 循环终止,然后返回 -1。那 while 循环什么时候应该终止?搜索区间为空的时候应该终止,意味着你没得找了,就等于没找到嘛。
while(left <= right) 的终止条件是 left == right + 1,写成区间的形式就是 [right + 1, right],或者带个具体的数字进去 [3, 2],可见这时候区间为空,因为没有数字既大于等于 3 又小于等于 2 的吧。所以这时候 while 循环终止是正确的,直接返回 -1 即可。
while(left < right) 的终止条件是 left == right,写成区间的形式就是 [left, right],或者带个具体的数字进去 [2, 2],这时候区间非空,还有一个数 2,但此时 while 循环终止了。也就是说这区间 [2, 2] 被漏掉了,索引 2 没有被搜索,如果这时候直接返回 -1 就是错误的。

2.此算法有什么缺陷?

比如说给你有序数组 nums = [1,2,2,2,3],target 为 2,此算法返回的索引是 2,没错。但是如果我想得到 target 的左侧边界,即索引 1,或者我想得到 target 的右侧边界,即索引 3,这样的话此算法是无法处理的。
这样的需求很常见,你也许会说,找到一个 target,然后向左或向右线性搜索不行吗?可以,但是不好,因为这样难以保证二分查找对数级的复杂度了。怎么说呢,比如一个数组
[1,2,3,4,5,5,5,5,5,1],让你找出target==5的右侧边界,你一次二分可以把5找出来,但是,你需要比较五次线性搜索才能才能找到右侧边界,是不是就很难保证时间复杂度

2.2LeetCode 69.x的平方根

题目描述:
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例 1:
输入: 4
输出: 2
示例 2:
输入: 8
输出: 2
说明: 8 的平方根是 2.82842…,
由于返回类型是整数,小数部分将被舍去。

双百二分:

class Solution {public:int mySqrt(int x) {if(x==1||x==0)return x;int left=0;int right=x;int mid;int ans;while(left<=right){mid=left+(right-left)/2;if((long long)mid*mid<=x){ans=mid;left=mid+1;}elseright=mid-1;}return ans;}
};

2.3LeetCode 374.猜数字大小

我们正在玩一个猜数字游戏。 游戏规则如下:
我从 1 到 n 选择一个数字。 你需要猜我选择了哪个数字。
每次你猜错了,我会告诉你这个数字是大了还是小了。
你调用一个预先定义好的接口 guess(int num),它会返回 3 个可能的结果(-1,1 或 0):
-1 : 我的数字比较小
1 : 我的数字比较大
0 : 恭喜!你猜对了!
示例 :
输入: n = 10, pick = 6
输出: 6

双百二分:

/** * Forward declaration of guess API.* @param  num   your guess* @return          -1 if num is lower than the guess number*                1 if num is higher than the guess number*               otherwise return 0* int guess(int num);*/
class Solution {public:int guessNumber(int n) {int left=0;int right=n;int mid;while(left<=right){mid=left+(right-left)/2;if(guess(mid)==1)left=mid+1;else if(guess(mid)==-1)right=mid-1;elsereturn mid;}return mid;}
};

2.4LeetCode33.搜索旋转排序数组

假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
示例 1:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
示例 2:
输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1

代码实现:

class Solution {public:int search(vector<int>& nums, int target) {int n = (int)nums.size();if (!n) return -1;if (n == 1) return nums[0] == target ? 0 : -1;int l = 0, r = n - 1;while (l <= r) {int mid = (l + r) / 2;if (nums[mid] == target) return mid;if (nums[0] <= nums[mid]) {if (nums[0] <= target && target < nums[mid]) {r = mid - 1;} else {l = mid + 1;}} else {if (nums[mid] < target && target <= nums[n - 1]) {l = mid + 1;} else {r = mid - 1;}}}return -1;}
};

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

3.1寻找左侧边界的二分搜索简介与代码模板

int left_bound(int[] nums, int target) {if (nums.length == 0) return -1;int left = 0;int right = nums.length; // 注意while (left < right) { // 注意int mid = (left + right) / 2;if (nums[mid] == target) {right = mid;//注意} else if (nums[mid] < target) {left = mid + 1;} else if (nums[mid] > target) {right = mid; // 注意}}return left;
}

可能你会存在很多疑问对这段代码,不急一个一个来看

1.为什么 while 中是 < 而不是 <=?

用相同的方法分析,因为 right = nums.length 而不是 nums.length - 1。因此每次循环的「搜索区间」是 [left, right) 左闭右开。
while(left < right) 终止的条件是 left == right,此时搜索区间 [left, left) 为空,所以可以正确终止。

PS:这里先要说一个搜索左右边界和上面这个算法的一个区别,也是很多读者问的:刚才的 right 不是 nums.length - 1 吗,为啥这里非要写成 nums.length 使得「搜索区间」变成左闭右开呢?

因为对于搜索左右侧边界的二分查找,这种写法比较普遍,我就拿这种写法举例了,保证你以后遇到这类代码可以理解。你非要用两端都闭的写法反而更简单,我会在后面写相关的代码,把三种二分搜索都用一种两端都闭的写法统一起来,你耐心往后看就行了。

2.while里为什么没有返回 -1 的操作?如果 nums 中不存在 target 这个值,怎么办?

因为要一步一步来,先理解一下这个「左侧边界」有什么特殊含义:

对于这个数组,算法会返回 1。这个 1 的含义可以这样解读:nums 中小于 2 的元素有 1 个。比如对于有序数组 nums = [2,3,5,7], target = 1,算法会返回 0,含义是:nums 中小于 1 的元素有 0 个。
再比如说 nums = [2,3,5,7], target = 8,算法会返回 4,含义是:nums 中小于 8 的元素有 4 个。
综上可以看出,函数的返回值(即 left 变量的值)取值区间是闭区间 [0, nums.length],所以我们简单添加两行代码就能在正确的时候 return -1:

 if(left != nums.size() && nums[left] == target) return left;return -1;

3.为什么 left = mid + 1,right = mid ?和之前的算法不一样?

这个很好解释,因为我们的「搜索区间」是 [left, right) 左闭右开,所以当 nums[mid] 被检测之后,下一步的搜索区间应该去掉 mid 分割成两个区间,即 [left, mid) 或 [mid + 1, right)。其实也就是while(left<right)中不带等于的原因。保证查找空间在每一步中至少有 2 个元素

4、为什么该算法能够搜索左侧边界?

关键在于对于 nums[mid] == target 这种情况的处理:

if (nums[mid] == target)right = mid;

5.能不能想办法把 right 变成 nums.length - 1,也就是继续使用两边都闭的「搜索区间」?这样就可以和第一种二分搜索在某种程度上统一起来了。

答案是当然可以,只要你明白了「搜索区间」这个概念,就能有效避免漏掉元素,随便你怎么改都行。下面我们严格根据逻辑来修改:因为你非要让搜索区间两端都闭,所以 right 应该初始化为 nums.length - 1,while 的终止条件应该是 left == right + 1,也就是其中应该用 <=,如下:

int left_bound(int[] nums, int target) {// 搜索区间为 [left, right]int left = 0, right = nums.length - 1;while (left <= right) {int mid = left + (right - left) / 2;if (nums[mid] < target) // 搜索区间变为 [mid+1, right]left = mid + 1;else if (nums[mid] > target) {// 搜索区间变为 [left, mid-1]right = mid - 1;else if (nums[mid] == target) {// 收缩右侧边界right = mid - 1;}
}

由于 while 的退出条件是 left == right + 1,所以当 target 比 nums 中所有元素都大时,会存在以下情况使得索引越界:

因此,最后返回结果的代码应该检查越界情况:

if (left >= nums.length || nums[left] != target)return -1;
return left;

所以完整的改进后:

int left_bound(int[] nums, int target) {int left = 0, right = nums.length - 1;// 搜索区间为 [left, right]while (left <= right) {int mid = left + (right - left) / 2;if (nums[mid] < target) {// 搜索区间变为 [mid+1, right]left = mid + 1;} else if (nums[mid] > target) {// 搜索区间变为 [left, mid-1]right = mid - 1;} else if (nums[mid] == target) {// 收缩右侧边界right = mid - 1;}}// 检查出界情况if (left >= nums.length || nums[left] != target)return -1;return left;
}

3.2LeetCode278. 第一个错误的版本

你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
示例:
给定 n = 5,并且 version = 4 是第一个错误的版本。
调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true
所以,4 是第一个错误的版本。

// The API isBadVersion is defined for you.
// bool isBadVersion(int version);class Solution {public:int firstBadVersion(int n) {int left=1;int right=n;int mid;while(left<=right){mid=left+(right-left)/2;if(isBadVersion(mid)==true)right=mid-1;else if(isBadVersion(mid)==false)left=mid+1;elseright=mid-1; }if(isBadVersion(left)==true)return left;return -1;}
};

3.3LeetCode 162. 寻找峰值

峰值元素是指其值大于左右相邻值的元素。
给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。
数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞。
示例 1:
输入: nums = [1,2,3,1]
输出: 2
解释: 3 是峰值元素,你的函数应该返回其索引 2。
示例 2:
输入: nums = [1,2,1,3,5,6,4]
输出: 1 或 5
解释: 你的函数可以返回索引 1,其峰值元素为 2;
或者返回索引 5, 其峰值元素为 6。
说明:
你的解法应该是 O(logN) 时间复杂度的。

首先要注意题目条件,在题目描述中出现了 nums[-1] = nums[n] = -∞,这就代表着 只要数组中存在一个元素比相邻元素大,那么沿着它一定可以找到一个峰值

class Solution {public:int findPeakElement(vector<int>& nums) {int left=0;int right=nums.size()-1;int mid;while(left<right){mid=left+(right-left)/2;if(nums[mid]>nums[mid+1])right=mid;elseleft=mid+1;}return left;}
};

3.4LeetCode 153. 寻找旋转排序数组中的最小值

假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。
你可以假设数组中不存在重复元素。
示例 1:
输入: [3,4,5,1,2]
输出: 1
示例 2:
输入: [4,5,6,7,0,1,2]
输出: 0

class Solution {public:int findMin(vector<int>& nums) {int left = 0;int right = nums.size() - 1;                /* 左闭右闭区间,如果用右开区间则不方便判断右值 */ while (left < right) {                      /* 循环不变式,如果left == right,则循环结束 */int mid = left + (right - left) / 2;    /* 地板除,mid更靠近left */if (nums[mid] > nums[right]) {          /* 中值 > 右值,最小值在右半边,收缩左边界 */ left = mid + 1;                     /* 因为中值 > 右值,中值肯定不是最小值,左边界可以跨过mid */ } else if (nums[mid] < nums[right]) {   /* 明确中值 < 右值,最小值在左半边,收缩右边界 */ right = mid;                        /* 因为中值 < 右值,中值也可能是最小值,右边界只能取到mid处 */ }}return nums[left];    /* 循环结束,left == right,最小值输出nums[left]或nums[right]均可 */     }
};

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

4.1寻找右侧边界的二分查找简介

int right_bound(int[] nums, int target) {int left = 0, right = nums.length - 1;while (left <= right) {int mid = left + (right - left) / 2;if (nums[mid] < target) {left = mid + 1;} else if (nums[mid] > target) {right = mid - 1;} else if (nums[mid] == target) {// 这里改成收缩左侧边界即可left = mid + 1;}}// 这里改为检查 right 越界的情况,见下图if (right < 0 || nums[right] != target)return -1;return right;
}

最后一段代码当 target 比所有元素都小时,right 会被减到 -1,所以需要在最后防止越界

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

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]

代码:

class Solution {public:vector<int> searchRange(vector<int>& nums, int target) {vector<int> vec;int left=0;int right=nums.size()-1;int mid;while(left<=right){mid=left+(right-left)/2;if(nums[mid]==target)right=mid-1;else if(nums[mid]>target)right=mid-1;elseleft=mid+1;}if(left==nums.size()||nums[left]!=target)vec.push_back(-1);elsevec.push_back(left);left=0;right=nums.size()-1;while(left<=right){mid=left+(right-left)/2;if(nums[mid]==target)left=mid+1;else if(nums[mid]<target)left=mid+1;elseright=mid-1;}if(right<0||nums[right]!=target)vec.push_back(-1);elsevec.push_back(right);return vec;}
};

二分查找(循序渐进由0到1掌握二分)相关推荐

  1. java二分查找递归_java学习之—递归实现二分查找法

    /** * 递归实现二分查找法 * Create by Administrator * 2018/6/21 0021 * 上午 11:25 **/ class OrdArray{ private lo ...

  2. android二分查找法简书,【PYTHON】二分查找算法

    二分查找: 在一段数字内,找到中间值,判断要找的值和中间值大小的比较. 如果中间值大一些,则在中间值的左侧区域继续按照上述方式查找. 如果中间值小一些,则在中间值的右侧区域继续按照上述方式查找. 直到 ...

  3. python二分查找算法_如何使用python的二分查找算法

    如何使用python的二分查找算法 发布时间:2020-11-18 09:27:29

  4. POJ 2785 有多少种4个数相加等于0的方案(二分查找 or hash)

    文章目录 1.二分查找法 1.1 思路: 1.2 AC代码 2.hash查找法 2.1 思路: 2.2 Wrong Answer 代码 2.3 Time Limit Exceeded 代码 2.4 偷 ...

  5. LeetCode简单题之二分查找

    题目 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1. 示例 1: 输入: n ...

  6. 面试前必知必会的二分查找及其变种

    今天给大家带来的是二分查找及其变种的总结,大家一定要看到最后呀,非常非常用心的一篇文章,废话不多说,让导演帮我们把镜头切到袁记菜馆吧! 袁记菜馆内.... 店小二:掌柜的,您进货回来了呀,哟!今天您买 ...

  7. java 二分查找_计算机入门必备算法——二分查找法

    1.引言 笔者对于计算机的研究一直停滞不前,近期想对一些算法进行复习和进一步的研究,每天都会更新一个新的算法,算法有难有易,层层递进.不希望能学的有多么高深,只希望在一些最基本的算法上有编码的思路,或 ...

  8. [二分查找] 一:子区间界限应当如何确定

    普通的二分查找 先引入一个最简单的二分查找吧 int binarySearch(int* nums, int numsSize, int target){int left = 0, right = n ...

  9. 二分查找--AVL查找树

    Data Mining 开启阅读模式 二分查找--AVL查找树 二分查找可以找到元素在数组中的下标位置,而查找树由于采用的是树结构,所以只能判断出元素是不是在树中以及给出最大/小值,而不能确定指定元素 ...

最新文章

  1. zabbix文档3.4-7配置
  2. 同步、异步 与 串行、并行的区别
  3. caffe学习(五):cifar-10数据集训练及测试(Ubuntu)
  4. ubuntu 局域网dns服务器_如何在 Ubuntu 16.04 服务器上配置内网 DNS 服务
  5. centos iptables_SQLyog远程连接centos中mysql数据库
  6. 设置组策略的应用条件-----Windows 管理规范 (WMI)过虑器
  7. linux下的源代码分析工具understand 2014100328681022
  8. 关于EMS,邮政快递包裹,邮政标准快递,邮政小包,EMS经济快递
  9. Star CCM+ 2206安装
  10. linux怎样安装麒麟双系统,手把手教您win10系统装麒麟系统双系统的解决办法
  11. 牛客寒假基础集训营 | Day1 G-eli和字符串
  12. 一文讲透商业智能BI 到底是什么[转]
  13. docker 进入容器内部及退出
  14. gitlab-runner 三种runner创建和和使用
  15. CSS——很多让p变红的方法
  16. 解除隐藏已购项目_从AppStore的已购项目中隐藏已经购买的APP
  17. 我真希望有人在我学计算机之前,就告诉了我这100多个程序员学习网站!【全编程人员都可以看】
  18. BEA-090403 Authentication for user admin denied
  19. 机器学习常见算法思想的面试宝典
  20. golang入门项目——Gin框架简易博客系统1.0

热门文章

  1. 三大新兴力量,引领中国消费新经济丨图媒体
  2. angular 动态取到的html片段 在页面的展示
  3. CAS单点登录0-原理
  4. 最近学习linux-c的编程
  5. Direct3D提高篇:HLSL编程实现PhotoShop滤镜效果(1)
  6. ORACLE表空间的相关操作
  7. 出租司机给我上的MBA课 -- [ 来自: ] [作者:cexo255]
  8. 【Python】 1055 集体照 (25 分)
  9. js es6数组常用方法:forEach map filter find every
  10. linux vscode配置spring boot开发环境