查找效率满分的算法—— “二分查找” 算法 (Java版)

本篇会加入个人的所谓鱼式疯言

❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言

而是理解过并总结出来通俗易懂的大白话,

小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的.

🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人能接受我们这个概念 !!!

在这里插入图片描述

前言

在前面的章节中,我们学习了 "双指针"算法“滑动窗口"算法

而在本篇章节 , 我们期待已久的 “二分查找”算法 即将登场, 透露下本次算法主要的规划 💖 💖 💖

目录

  1. 二分算法的初识

  2. 二分算法的应用

  3. 二分算法的总结

一. 二分算法的初识

1. 二分算法的简介

二分算法,也被称为 二分查找 算法,是一种常用的查找 算法。

它的基本思想是将已排序的数据序列分成 两部分取中间 的元素与 目标值 进行比较,然后根据比较结果,确定目标值 在左半部分还是 右半部分,再继续在相应的部分进行 查找,直到找到目标值或者确定目标值 不存在 为止。

2. 二分算法的使用步骤

因为二分查找算法分为两种:

“一种是 朴素二分查找” 算法, 另外一种是 "左右边界二分查找 " 算法

因为朴素二分查找比较基础,我们先学习基础版本的,再调整有难度的 💖 💖 💖

小编在这里说明 “朴素二分查找” 的具体步骤哦

先拿个题目来瞧瞧呗

二分查找

704.二分查找题目链接

<1>. 题目描述

在这里插入图片描述

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

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

题目含义

在给定的数组中查找一个数 ,返回其 下标 ,如果没有就返回 -1

<2>. 讲解算法思想

题目分析 : 我们要查找一个数最简单的方式

解法一

暴力求解

用一个 for -遍历 数组 ,然后中间用一个 if 来判断 ,一定成立就返回其下标

在解法一的基础上,我们为了减少一半的数据,特别提炼出 二分查找 算法的思想

解法二

二分算法 :

请添加图片描述

  • 首先,确定要查找的目标值在序列中的可能范围,通常是通过比较目标值和序列的 第一个元素最后一个元素 来确定;

我们定义 一个数组的第一个元素的为 left , 再定义 最后一个元素 的下标为 right

  • 然后,在可能范围内,取中间的元素与目标值进行比较;

  • 如果中间的元素等于目标值,则查找成功,返回对应的位置;

  • 如果中间的元素 大于 目标值,则说明目标值可能在 左半部分 ,缩小范围到 左半部分 继续查找,重复步骤2;

  • 如果中间的元素 小于 目标值,则说明目标值可能在 右半部分 ,缩小范围到 右半部分 继续查找,重复步骤2;

  • 如果范围缩小到只有一个元素,但该元素不等于目标值,则查找失败,目标值不存在。

  • 二分算法的时间复杂度为 O(log n) ,其中 n是序列的长度 。``二分算法 通常适用于 已排序的数据序列,能够 快速查找目标值 的位置。

<3>. 编写代码

class Solution {public int search(int[] nums, int target) {int numslen = nums.length;int left=0,right=numslen-1;while(left <= right) {int mid= left + ((right - left) >>> 1);if(nums[mid] > target) {right = mid - 1;} else if(nums[mid] < target ) {left = mid + 1;} else {return mid;}}return -1;}
}

在这里插入图片描述

鱼式疯言

朴素二分的算法体会就是

小了 整体新的区间移动到 中间值的右边

大了 整体新的区间移动到 中间值的左边

并且 朴素二分 必须是保证数据是有序的

 while(left <= right) {};

细节注意

我们需要这里要取等的, 当 leftright 相等 的时候, 也有可能是 目标值

二. 二分算法的应用

讲解完了 朴素的二分算法, 接下来讲解了 左右边界二分查找 算法

小编在这里说一句哦,这个 左右边界的 二分算法思想

可以这么说,是 基础算法 细节最多最恶心 ,也是 最容易造成死循环 的一种算法

1. 寻找峰值

162.寻找峰值题目链接

<1>. 题目描述

在这里插入图片描述

峰值元素是指其值严格大于左右相邻值的元素。

给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞ 。

你必须实现时间复杂度为 O(log 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。

题目含义 :

在一个数组寻找一个既 小于左边小于右边 的 一个数字的 下标

<2>. 讲解算法思想

其实本质上我们可以认为是寻找一个 极大值(也可以是最大值)

解法一 :

暴力遍历 :

我们只需要遍历数组即可查找到我们 极大值

解法二 :

二分算法

当我们需要寻找一个 最大值 的 时候, 我们可以通过 单调性 来寻找 二段性

什么是 二段性 , 就是在数组中可以区分为 两个区域 ,我们可以把这个区域划分为 两段 ,而这个 两端 我们可以进行分为

第一个区域的 右边界 ,以及相邻的第二个区域的 左边界

  1. 首先我们定义 left第一个元素 下标 , right最后 一个元素下标
  1. 如果 mid递增 区间, 就让 left = mid
  1. 如果 mid递减 区间, 就让 right = mid - 1
  1. 最后就是我们需要注意的就是 终止条件 怎么设置
  1. 递减区间 边界的 right 刚好跳到 左边 ,而 left 也刚刚好处于 递增区间 的时我们就可以确定右边界了

所以我们 只需要让 left 等于 right

请添加图片描述

<3>. 编写代码

class Solution {public int findPeakElement(int[] nums) {// 进行 左二分查找算法int left=0,right=nums.length-1;while(left < right) {int mid= left +  ( (right - left + 1) >>> 1 );if(nums[mid] > nums[mid-1]) {left = mid;} else {right = mid -1;}}return right;        }
}

在这里插入图片描述

鱼式疯言

小编的体会

  1. 对于二分算法来说,二段性 才是 核心 , 如何寻找到在 一个区间 中划分为 两个 不同含义 的区间,

从而利用二分法寻找左右边界来得到目标值

本题寻找二段性 常见的方式: 递增性

细节处理:

 int mid= left +  ( (right - left + 1) >>> 1 );

这里我们需要用到 当我们用到 right = mid - 1 ;

自然我们就需要 在这里进行 +1 操作

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

在排序数组中查找元素中 第一个位置和最后一个位置题目链接

<1>. 题目描述

在这里插入图片描述

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:

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

题目含义 :

寻找一段相同目标值的 第一个位置最后一个位置下标

<2>. 讲解算法思想

题目分析:

用的还是二分思想,就是根据数据的性质,在某种判断条件下将区间 一分为二 ,然后舍去其中一个
区间,然后再另一个区间内查找;方便叙述,用 target 表示该元素, left 表示 左边界right 表示右边界。

当我们求左边界时, 就可以按照

寻找左边界:

  • 我们注意到以左边界划分的两个区间的特点:

  • 左边区间 [left, left - 1] 都是小于 target 的;

  • 右边区间(包括左边界) =[left, right] 都是 大于等于 x 的;

  • 因此,关于 mid 的落点,我们可以分为下面两种情况:

  • 当我们的 mid 落在 [left, left - 1] 区间的时候,也就是 arr[mid] <
    target

  • 说明 [left, mid] 都是可以舍去的,此时更新 leftmid + 1 的位置,
    继续在 [mid + 1, right] 上寻找左边界;

  • mid 落在 [left, right] 的区间的时候,也就是 arr[mid] >= target
    说明 [mid + 1, right] (因为 mid 可能是最终结果,不能舍去)是可以舍去的,此时
    更新 rightmid 的位置,继续在 [left, mid] 上寻找 左边界

请添加图片描述

当我们需要右边界时

按照上面我们寻找 左边界 的思维来寻找 右边界

我们就以 右边界 的来划分区域

mid 处于 <= target 时 ,right = mid

mid 处于 > target 时 , left = mid -1

请添加图片描述

<3>. 编写代码

class Solution {public  int[] searchRange(int[] nums, int target) {// 定义一个存放起始和终止位置的数组int[] ret=new int[]{-1,-1};// 得到长度int len=nums.length;// 数组为 空 时 就直接返回if(len == 0) return ret;// 进行二分操作// 定义左右指针int left=0,right= len-1;/*** 寻找左边界* 当 right = mid = left 就会陷入死循环* 细节一 : 终止条件 不能写等号** 当 中点值  <= target 时* 细节二 : 我们更新 left = mid** 当 left  mid  right  相邻 时*  细节三 : 更新 mid = left + ( (right - left + 1) >>> 1 )**///while(left < right) {int mid=left + ( (right-left) >>> 1);if(nums[mid] < target) {left = mid+1;}  else if(nums[mid] >= target) {right = mid ;}}if(nums[right] == target) ret[0] = right;left=0;right=len-1;/*** 寻找右边界**  当 right = mid = left 就会陷入死循环*  细节一 : 循环终止条件 : 这里不可以进行 取等**  当 中点值 >= target  时 进行 right 移动*  细节二 : 右指针 right 移动的位置 是 到达  中点下标 mid***  left  mid  right*  当 中点 和 left  right 相邻 时就需要*  细节三 : 确立 mid = left +(  (right - left ) >>> 1 )*/while(left < right) {int flg=left + ( (right - left + 1) >>> 1);if(nums[flg] <= target) {left = flg;}  else if(nums[flg] > target) {right = flg -1;}}if (nums[left]== target) ret[1]= left;return ret;}
}

在这里插入图片描述

鱼式疯言

说下对于本题小编个人的体会吧

二段性 我们是找到了,但这里的是如何去 划分我们的区域

所以然后选择 等号 也是很关键的一条

当我们寻找 左边界 时,我们就需要 让 mid >= target 的情况进行划分

当我们寻找 右边界 时, 我们就需要 让 mid < target 的情况进行划分\

细节处理

处理 右边界 的时

 while(left < right) {}

right = mid = left 就会陷入 死循环

细节一 :

终止条件 不能写等号

   if(nums[flg] <= target) {left = flg; }

中点值 <= target

细节二 :

我们更新 left = mid

left mid right 相邻 时

细节三 :

更新 mid = left + ( (right - left + 1) >>> 1 )

处理 左边界

 while(left < right) {}

right = mid = left 就会陷入 死循环

细节一 :

终止条件 不能写等号

   if(nums[flg] >= target) {right = flg; }

中点值 <= target

细节二 :

我们更新 right = mid

left mid right 相邻 时

细节三 :

更新 mid = left + ( (right - left ) >>> 1 )

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

寻找排序数组中的最小值题目链接

<1>. 题目描述

在这里插入图片描述

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:

若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]

若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]

注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。

示例 2:

输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 3 次得到输入数组。

示例 3:

输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。

** 题目含义** :

在一个由原先 有序 进行 旋转 后的数组中,寻找 最小值

<2>. 讲解算法思想

题目分析

因为题目要求时间 复杂度只能是 O(log n)

所以这样我们就需要用到 二分算法

那我们就必须寻找到 二段性

首先还是利用的大小关系来寻找我们的 基准值

因为是 反转后的数据,所以从最小值到最后一个元素必然是 递增 的,

那么这段区间肯定都是 小于等于 最后一个元素的

在这里插入图片描述

翻转过去的元素 必然是 大于 我们最后一个元素的,我们又划分出了 这段区间

** 二分算法**

所以我们操作数字定义 left= 0 ,right 指向最后一个元素 , 并且取一个 基准值 target 也为最后一个元素

然后进行 mid 的判断,以及 rightleft 的移动,

最终 leftright 相遇的位置就是我们要找的 最小值

请添加图片描述

<2>.编写代码

class Solution {public int findMin(int[] nums) {// 进行二分左查找 int left= 0, right=nums.length-1;// 以 最右边为基准值 进行 原数组的划分int flg=nums[right];while(left < right) {// 得到中间 下标int mid= left + (  (right - left ) >>> 1 );// 如果 小于 基准   说明 是 左边的数 , 不存在最小值if( nums[mid]  >  flg) {left = mid +1;} else {// 存在 右边 小于或等于 基准值    right =mid;}}  return nums[left];}}

在这里插入图片描述

三. 二分算法的总结

我们先初步认识了什么是 二分算法以及并明白了 朴素二分查找算法 的使用,

并且 朴素 是在 有序 的条件下进行

之后我们又通过 “寻找峰值”“在排序数组中查找元素中 第一个位置和最后一个位置”"寻找排序数组中的最小值" , 我们更明白 寻找 二段性 比如通过 比大小, 单调性端点值, 不等性

下面有 小彩蛋

总结朴素二分算法模板

在这里插入图片描述

总结左右边界二分算法模板

记住一点:

求左边界时: right = mid

求右边界时: left = mid

在这里插入图片描述

如果觉得小编写的还不错的咱可支持 三连 下 (定有回访哦) , 不妥当的咱请评论区 指正

希望我的文章能给各位宝子们带来哪怕一点点的收获就是 小编创作 的最大 动力 💖 💖 💖

在这里插入图片描述

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

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

相关文章

深入了解Nginx(一):Nginx核心原理

一、Nginx核心原理 本节为大家介绍Nginx的核心原理,包含Reactor模型、Nginx的模块化设计、Nginx的请求处理阶段. &#xff08;本文源自微博客,且已获得授权&#xff09; 1.1、Reactor模型 Nginx对高并发IO的处理使用了Reactor事件驱动模型。Reactor模型的基本组件包含时间收集…

使用xsd验证xml格式的正确性

1.1 基础知识介绍 XML简介&#xff1a;XML是可扩展标记语言&#xff08;eXtensible Markup Language&#xff09;的缩写&#xff0c;它是一种数据表示格式&#xff0c;可以描述非常复杂的数据结构&#xff0c;常用于传输和存储数据。xml文件、xml消息。XSD简介&#xff1a;是X…

LabVIEW和ZigBee无线温湿度监测

LabVIEW和ZigBee无线温湿度监测 随着物联网技术的迅速发展&#xff0c;温湿度数据的远程无线监测在农业大棚、仓库和其他需环境控制的场所变得日益重要。开发了一种基于LabVIEW和ZigBee技术的多区域无线温湿度监测系统。系统通过DHT11传感器收集温湿度数据&#xff0c;利用Zig…

uniapp-自定义navigationBar

封装导航栏自定义组件 创建 nav-bar.vue <script setup>import {onReady} from dcloudio/uni-appimport {ref} from vue;const propsdefineProps([navBackgroundColor])const statusBarHeight ref()const navHeight ref()onReady(() > {uni.getSystemInfo({success…

图生代码,从Hello Onion 代码开始

从Hello Onion 代码开始 1&#xff0c;从代码开始 原生语言采用java 作为载体。通过注解方式实现“UI可视化元素"与代码bean之间的映射. 转换示例 2&#xff0c;运行解析原理 在执行JAVA代码期间&#xff0c;通过读取注解信息&#xff0c;转换为前端的JSON交由前端JS框…

c++设计模式-->访问者模式

#include <iostream> #include <string> #include <memory> using namespace std;class AbstractMember; // 前向声明// 行为基类 class AbstractAction { public:virtual void maleDoing(AbstractMember* member) 0;virtual void femaleDoing(AbstractMemb…

安装Pnetcdf顺便升级autoconf与automake

Netcdf NetCDF&#xff08;Network Common Data Form&#xff09;是一种用于存储科学数据的文件格式和软件库。它是一种自描述、可移植且可扩展的数据格式&#xff0c;广泛应用于气象学、海洋学、地球科学和其他领域的科学研究。 NetCDF文件以二进制形式存储&#xff0c;结构…

Qt | QGridLayout 类(网格布局)

01、上节回顾 Qt | QBoxLayout 及其子类(盒式布局)02、QGridLayout 简介 1、网格布局原理(见下图): 基本原理是把窗口划分为若干个单元格,每个子部件被放置于一个或多个单元格之中,各 单元格的大小可由拉伸因子和一行或列中单元格的数量来确定,若子部件的大小(由 sizeH…

Vue从入门到实战 Day08~Day10

智慧商城项目 1. 项目演示 目标&#xff1a;查看项目效果&#xff0c;明确功能模块 -> 完整的电商购物流程 2. 项目收获 目标&#xff1a;明确做完本项目&#xff0c;能够收获哪些内容 3. 创建项目 目标&#xff1a;基于VueCli自定义创建项目架子 4. 调整初始化目录 目…

网络安全之BGP详解

BGP&#xff1b;边界网关协议 使用范围&#xff1b;BGP范围&#xff0c;在AS之间使用的协议。 协议的特点&#xff08;算法&#xff09;&#xff1a;路径矢量型&#xff0c;没有算法。 协议是否传递网络掩码&#xff1a;传递网络掩码&#xff0c;支持VLSM&#xff0c;CIDR …

ASP+ACCESS基于B2C电子商务网站设计

摘 要 运用ASP技术结合了Access数据库原理&#xff0c;基于B/S模式我们开发了一个网上购物系统。在我们的系统中&#xff0c;顾客可以很方便的注册成为会员&#xff0c;对商品进行浏览检索&#xff0c;查看商品的详细资料&#xff0c;然后根据各人的喜好购买心仪的商品。系统…

CCF20220901——如此编码

CCF20220901——如此编码 代码如下&#xff1a; #include<bits/stdc.h> using namespace std; int main() {int n,m,cnt1,a[1000],c[1000]{1};cin>>n>>m;for(int i1;i<n;i){cin>>a[i];cnt*a[i];c[i]cnt;}int b[1000]{0};for(int i1;i<n;i)b[i](…

JPHS-JMIR Public Health and Surveillance

文章目录 一、期刊简介二、征稿信息三、期刊表现四、投稿须知五、投稿咨询 一、期刊简介 JMIR Public Health and Surveillance是一本多学科期刊&#xff0c;专注于公共卫生创新与技术的交叉领域&#xff0c;包括公共卫生信息学、监测&#xff08;监测系统和快速报告&#xff…

CCF20220601——归一化处理

CCF20220601——归一化处理 代码如下&#xff1a; #include<bits/stdc.h> using namespace std; int main() {int n,a[1000],sum0;scanf("%d",&n);for(int i1;i<n;i){scanf("%d",&a[i]);suma[i];}double aver1.0,b0.0,d1.0;aversum/(n*1…

Java基础(三)- 多线程、网络通信、单元测试、反射、注解、动态代理

多线程基础 线程&#xff1a;一个程序内部的一条执行流程&#xff0c;只有一条执行流程就是单线程 java.lang.Thread代表线程 主线程退出&#xff0c;子线程存在&#xff0c;进程不会退出 可以使用jconsole查看 创建线程 有多个方法可以创建线程 继承Thread类 优点&#x…

【学习】实验室服务器常用的Linux指令。

1. 下载GitHub代码。 使用代码&#xff1a; git clone https://github.com/Turoad/CLRNet.git2. 压缩 / 解压。 打包压缩 是日常工作中备份文件的一种方式 在不同操作系统中&#xff0c;常用的打包压缩方式是不同的选项 含义 Windows 常用 rarMac 常用 zipLinux 常用 tar.gz…

学硕都考11408的211院校!河北工业大学计算机考研考情分析!

河北工业大学&#xff08;Hebei University of Technology&#xff09;&#xff0c;简称河北工大&#xff0c;坐落于天津市&#xff0c;由河北省人民政府、天津市人民政府与中华人民共和国教育部共建&#xff0c; 隶属于河北省&#xff0c;是国家“双一流”建设高校、国家“211…

自动化测试在软件开发生命周期中如何提高代码质量?

自动化测试是一种在软件开发生命周期中使用软件工具来执行测试的方法&#xff0c;它可以大大提高代码质量&#xff0c;减少开发过程中的错误和缺陷。本文将从零开始&#xff0c;详细且规范地介绍如何使用自动化测试来提高代码质量。 第一步&#xff1a;明确测试目标 在开始自…

webgl入门-绘制三角形

绘制三角形 前言 三角形是一个最简单、最稳定的面&#xff0c;webgl 中的三维模型都是由三角面组成的。咱们这一篇就说一下三角形的绘制方法。 课堂目标 理解多点绘图原理。可以绘制三角形&#xff0c;并将其组合成多边形。 知识点 缓冲区对象点、线、面图形 第一章 web…

iPhone实况照片从Windows资源管理器复制的JPG+MOV无法正常还原到iPhone

背景&#xff1a; 之前使用的iPhone 15 Pro&#xff0c;使用的Windows资源管理器当中复制导出的实况照片&#xff0c;复制出来的格式例如IMG_0001.JPG, IMG_0001.MOV。之后手机就卖掉了。现在使用的iPhone 14 Pro Max&#xff0c;想要导回之前备份的实况照片。尝试使用爱思助手…