二分查找

  • 二分法的引入
    • 情形1
      • 1.X的平方根
      • 2.搜索旋转排序数组
    • 情形2
      • 1.第一个错误的版本
      • 2.寻找峰值
      • 3.寻找旋转排序数组中的最小值
    • 情形3
      • 在排序数组中查找第一个和最后一个位置

当遇到查找特定元素的时候,最容易想到的就是暴力解法,直接遍历,这种做法简单粗暴,可以解题,但是时间复杂度过高。所以我们可以用二分法来提高效率。

二分法的引入

二分查找一般用于数据是有序的,我们要找到目标元素target,可以通过循环或者递归在每一次比较以后都把查找范围缩小一半,在剩下范围接着查找。接下来,我就来介绍一下二分查找。我总结了几种不同的清况用到的二分模板,不过大家还是要自己动手做题,多画图。

情形1

二分查找最基本的形式就是查找条件可以不需要和元素两侧进行比较来确定。

初始条件:left = 0, right = length-1
终止:left > right
向左查找:right = mid-1
向右查找:left = mid+1

模板一的代码

int binarySearch(int[] nums, int target){//如果数组为空或者长度为0,我们肯定找不到我们想要查找的元素if(nums == null || nums.length == 0)return -1;
//用左右指针来分别指向查找区间的左边界和右边界int left = 0, right = nums.length - 1;while(left <= right){//  这边涉及到数据溢出的问题int mid = left + (right - left) / 2;if(nums[mid] == target){ return mid; }else if(nums[mid] < target) { left = mid + 1; }else { right = mid - 1; }}//  return -1;
}

mid=(left+right)/2也可以,那为什么要写成mid=left+(right-left)/2;
这是为了防止数据溢出,假设数据最多存储范围是0~255。
left=200,right=240,这个时候在执行left+right的时候就已经超过范围了,所以为了防止数据溢出要写成mid=left+(right-left)/2

接下来通过实战来检验一下自己的学习成果

1.X的平方根

力扣–X的平方根

题目描述
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

示例 1:

输入:x = 4 输出:2 示例 2:

输入:x = 8 输出:2 解释:8 的算术平方根是 2.82842…, 由于返回类型是整数,小数部分将被舍去。

来源:力扣(LeetCode)

class Solution {public int mySqrt(int x) {//二分查找,由于是找算数平方根,所以肯定找得到//如果是0或1的话,直接返回if(x==0 || x==1) return x;//开始找int low=1;int high=x;int mid;while( low <=high){mid=(low + high)/2;if(mid> x /mid){high=mid-1;}else if(mid  < x/mid){low=mid+1;}else{return mid;          }}return high;}
}

为什么当条件不满足的时候,要返回low-1或high,我来说明一下,以目标元素是8为例。

2.搜索旋转排序数组

力扣-搜索旋转排序数组
题目描述

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为
[nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …,
nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为
[4,5,6,7,0,1,2] 。

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1

示例 1:

输入:nums = [4,5,6,7,0,1,2], target = 0 输出:4 示例 2:

输入:nums = [4,5,6,7,0,1,2], target = 3 输出:-1 示例 3:

输入:nums = [1], target = 0 输出:-1

提示:

1 <= nums.length <= 5000
-10^4 <= nums[i] <= 10^4 nums 中的每个值都 独一无二 题目数据保证 nums 在预先未知的某个下标上进行了旋转
-10^4 <= target <= 10^4

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/search-in-rotated-sorted-array

class Solution {public int search(int[] nums, int target) {int low = 0, high = nums.length - 1, mid = 0;while (low <=high) {mid = low + (high - lo) / 2;if (nums[mid] == target) {return mid;}// 先根据 nums[mid] 与 nums[low] 的关系判断 mid 是在左段还是右段 if (nums[mid] >= nums[low]) {// 再判断 target 是在 mid 的左边还是右边,从而调整左右边界 low 和 highif (target >= nums[low] && target < nums[mid]) {high = mid - 1;} else {low = mid + 1;}} else {if (target > nums[mid] && target <= nums[high]) {low = mid + 1;} else {high = mid - 1;}}}return -1;}
}

思路分析

情形2

查找条件需要访问元素的相邻的右边元素。
根据是否满足条件,来决定是向左还是向右。
保证查找空间在每一步中至少有 2 个元素。
需要进行后处理。 当你剩下 1 个元素时,循环 / 递归结束。 需要评估剩余元素是否符合条件。

初始条件:left = 0, right = length
终止:left == right
向左查找:right = mid
向右查找:left = mid+1

1.第一个错误的版本

力扣-第一个错误的版本

你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。

假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version
是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

示例 1:

输入:n = 5, bad = 4 输出:4 解释: 调用 isBadVersion(3) -> false 调用
isBadVersion(5) -> true 调用 isBadVersion(4) -> true 所以,4 是第一个错误的版本。 示例
2:

输入:n = 1, bad = 1 输出:1

作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/binary-search/xepthr/

这题就是要找到第一个出现错误的版本,如果当前版本正确,继续往下找之后的版本,如果当前版本错误,就看一下上一个版本是否正确,因为题目说出现错误版本,说明最后肯定有错误的版本。

/* The isBadVersion API is defined in the parent class VersionControl.boolean isBadVersion(int version); */public class Solution extends VersionControl {public int firstBadVersion(int n) {int low=1;int high=n;int mid;while(low<high){mid=low+(high-low)/2;if(isBadVersion(mid)){//区间在[low,mid]high=mid;}else{low=mid+1;}}return low;}
}

2.寻找峰值

力扣-寻找峰值

我们可以看出来这题他肯定是有峰值的,我们可以用二分法,然后比较当前元素和相邻元素的大小,通过二分不断逼近。

class Solution {public int findPeakElement(int[] nums) {int low=0;int high=nums.length-1;int mid;while(low < high){mid=low+(high-low)/2;if(nums[mid] > nums[mid+1] ){//说明峰值在左边high=mid;}else{low=mid+1;}}return low;}
}

3.寻找旋转排序数组中的最小值

这道题目挺有难度的,大家可以仔细想想

class Solution {public int findMin(int[] nums) {int low = 0;int high = nums.length - 1;while (low < high) {int mid= low + (high - low) / 2;if (nums[mid] < nums[high]) {high = mid;} else {low = mid + 1;}}return nums[low];}
}

情形3

每一次查找范围中都有三个元素,要根据相邻元素来进行比较,从而判断向左还是向右
初始条件:left = 0, right = length-1
终止:left + 1 == right
对于我们初学的来来说,我们应该分别计算左边界和右边界,不要想着一下子解决,做题目要有耐心,不要想当然,多画图,可能画图分析自己就会了。

在排序数组中查找第一个和最后一个位置

在排序数组中查找第一个和最后一个位置

分析:
根据题目要求,我们知道目标元素在数组中可能出现多次,所以我们就需要找到这个元素的左边界和右边界,我们就可以使用两个二分查找来找到左边界和右边界。

class Solution {public int[] searchRange(int[] nums, int target) {int leftBorder = getLeftBorder(nums, target);int rightBorder = getRightBorder(nums, target);// 情况一if (leftBorder == -2 || rightBorder == -2) return new int[]{-1, -1};// 情况三if (rightBorder - leftBorder > 1) return new int[]{leftBorder + 1, rightBorder - 1};// 情况二return new int[]{-1, -1};}int getRightBorder(int[] nums, int target) {int left = 0;int right = nums.length - 1;int rightBorder = -2; // 记录一下rightBorder没有被赋值的情况while (left <= right) {int middle = left + ((right - left) / 2);if (nums[middle] > target) {right = middle - 1;} else { // 寻找右边界,nums[middle] == target的时候更新leftleft = middle + 1;rightBorder = left;}}return rightBorder;}int getLeftBorder(int[] nums, int target) {int left = 0;int right = nums.length - 1;int leftBorder = -2; // 记录一下leftBorder没有被赋值的情况while (left <= right) {int middle = left + ((right - left) / 2);if (nums[middle] >= target) { // 寻找左边界,nums[middle] == target的时候更新rightright = middle - 1;leftBorder = right;} else {left = middle + 1;}}return leftBorder;}
}

二分查找模板全面总结相关推荐

  1. 二分查找模板及题目汇总

    文章目录 经典二分查找模板 Templete1 Templete2 Templete3 查找第一次出现target的位置 查找最后一次出现target的位置 查找与target最接近的数的位置 第一个 ...

  2. 不需要考虑mid+1、mid-1的二分查找模板,希望大家都能学会

    文章目录 一.模板示范 二.模板 三.细节说明 为什么L的初始值为-1,R的初始值为N 为什么循环结束的条件是while(L+1!=R)? 不会陷入死循环 最后 四.     例题one[数的范围]( ...

  3. 魔鬼一样的二分查找模板

    Although the basic idea of binary search is comparatively straightforward, the details can be surpri ...

  4. P1873 砍树(二分查找模板)

    题目描述 伐木工人米尔科需要砍倒 MM 米长的木材.这是一个对米尔科来说很容易的工作,因为他有一个漂亮的新伐木机,可以像野火一样砍倒森林. 米尔科的伐木机工作过程如下:米尔科设置一个高度参数 HH(米 ...

  5. 【二分查找万能模板,告别死循环、告别越界】Leecode 34. 在排序数组中查找元素的第一个和最后一个位置

    题目链接:https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/ 题解汇总: ...

  6. 二分查找及模板——试解《数的范围》

    目录 一.前言 二.二分查找模板 2.1.模板: 三.例题 3.1.数的范围: 四.总结: 4.1.小tips 五.后记 一.前言 晚上好...(哈欠 今天我们来讲什么呢?唔...我们来讲一个二分模板 ...

  7. LeetCode Hot100 ---- 二分查找专题

    什么是二分查找 二分查找是计算机科学中最基本.最有用的算法之一. 它描述了在有序集合中搜索特定值的过程. 二分查找中使用的术语: 目标 Target -- 你要查找的值 索引 Index -- 你要查 ...

  8. [算法][二分查找][排除法]

    最基本的二分查找算法 「搜索区间」是 [left, right] nums[mid] == target 时可以立即返回 int binary_search(int[] nums, int targe ...

  9. 天池 在线编程 木材加工(二分查找)

    文章目录 1. 题目 2. 解题 1. 题目 有一些原木,现在想把这些木头切割成一些长度相同的小段木头,需要得到的小段的数目至少为 k. 当然,我们希望得到的小段越长越好,你需要计算能够得到的小段木头 ...

最新文章

  1. tstringgrid 单元格区域获取_对合并单元格的序号填充还在一个个的手动录入?那就真的Out了!...
  2. nginx phase handler的原理和选择
  3. Android开发之开发工具之Android Studio出现全屏的解决办法
  4. 重复数据删除:块级技术VS.字节级技术
  5. ASP.NET MVC5 + EF6 入门教程 (5) Model和Entity Framework
  6. jmeter从mysql取值_Jmeter获取数据库值并作为参数请求(转载)
  7. 冻存样品对单细胞测序影响大吗?
  8. mysql小黑屏怎么用,兰蔻小黑瓶怎么用?多种使用方法教给大家
  9. JM8.6之get_block() 1/4亮度像素内插详述
  10. js内公切线切点的计算
  11. java 开发平台 普元_普元开发平台好不好?
  12. 生存模型的calibration需要注意的一个问题
  13. 一款功能强大的Web接口和网页测试工具
  14. 13-反向传播法求梯度
  15. pycharm和Anaconda强强联手
  16. 读书笔记——寻找道德
  17. 自主开发的小型Web服务器
  18. Qt数据库应用23-个人信息报表
  19. 游戏测试中容易被忽视的重点
  20. 微众银行张开翔: FISCO BCOS - 开放的区块链实践之道 | 11月25日【区块链技术与实践】论坛...

热门文章

  1. 【Docker】Docker的三大核心组件
  2. docker-compose的安装与简单使用
  3. Windows10快捷应用指令
  4. xgboost lightgbm catboost 多分类 多标签
  5. Druid数据库连接池使用参考
  6. LeetCode简单题之最常见的单词
  7. 前后端分离必备工具:Swagger快速搞定(整合SpringBoot详细教程)
  8. SpringBoot-web开发(一): 静态资源的导入(源码分析)
  9. 大数据调度平台Airflow(八):Airflow分布式集群搭建及测试
  10. 2021年腾讯云安装Docker最简洁方法