二分专题----如何优雅的写出二分

embedded/2024/11/14 12:48:47/

目录

一、如何编写二分

二、题目练习

1、二分查找----点击跳转题目

2、在排序数组中查找元素的第⼀个和最后⼀个位置 点击跳转题目

3、搜索插⼊位置----点击跳转题目

4、x的平⽅根----点击跳转题目

5、⼭峰数组的峰顶---点击跳转题目

6、寻找峰值----点击跳转题目

7、搜索旋转排序数组中的最⼩值----点击跳转题目


本文使用的方法是基于b站up-- 五点七边 的视频---点击跳转视频

二分算法是一种在有序数组中查找特定元素的高效算法。它的基本思想是通过不断将数组分成两部分,然后比较目标元素与中间元素的大小,从而逐步缩小查找范围,直到找到目标元素或确定目标元素不存在。

二分算法针对数组的查找算法,要求数组具有单调性,但请不要把单调性简单理解为数字的递增递减,其实只要是数组,无论是什么类型的数组,只要满足单调性,都可以使用二分算法;那究竟这个单调性是什么意思呢?

当我们需要在数组中查找某一元素时,如果我们能根据数组和元素的特征,把数组分成两部分,两部分各自持有不同的性质,那么就可以使用二分算法

红蓝各自代表一种性质,此时,红蓝的边界就是我们查找的元素

一、如何编写二分

我们从前学习的二分算法是定义两个指针l、r,把l~r区间视为有效搜索区间,通过不断的对[l , r]的中点判断性质为缩小l~r区间,当区间长度为1时(l=r)即找到了目标,区间长度为0时(l>r)代表没有符合的目标,但如果按照这种搜索有效区间的思路来编写代码时,在代码的细节问题上总是容易出现边界问题,为了处理各种各样的边界问题,又有几种对应的二分模板;其实如果换一种角度,可以把这些二分模板统一为一个,这篇文章我们换一种角度来编写二分,使得二分算法有且仅有一个统一的模板。

我们暂且把这种方法叫做红蓝染色法:把待搜索的数组看成灰色(待搜索),把我们挖掘出来的性质分别看作红色和蓝色,所谓二分,此时就变成了不断判断数组元素的性质,对数组染色的过程,查找完成时,数组完全被红色、蓝色占据,红蓝边界即为查找结果。

此时再理解二分,其实就是想象一个挡板(黄色),把数组分为两部分(蓝、红),通过不断的迭代,l会移动到黄色的左边,r会移动到黄色的右边,再根据题意酌情返回l或者r。

伪代码如下:

l=-1, r=N  //在(l,r)的数组索引开区间内,数组元素都是灰色的while l+1≠r  //l+1=r时,蓝红区域接触m = ⌊(l+r)/2⌋  //开区间中间元素if IsBlue(m)l=m  //蓝色区域向右拓展到中间元素elser=m  //红色区域向左拓展到中间元素//根据实际情况返回l或r,但要注意:返回谁就要对谁做越界判断!
return l or r  

需要注意的是,一开始定义l、r指针时,它们是指向数组外的,这样才能代表数组是灰色待搜索的,也就是刚开始蓝色区域、红色区域在数组索引区域外,因为数组区域可能全蓝或全红,为保证考虑到所有情况不得不这么做。

二、题目练习

1、二分查找----点击跳转题目

题意:在单调递增的数组中查找值为target,可能不存在target

思考:把数组以target分割,左边元素的性质为:小于等于target;右边元素的性质为:大于target;最后找到的分界线中,左边为l,右边为r,如果l位置的元素不等于target则数组中没有值为target的元素

class Solution {
public:int search(vector<int>& nums, int target) {int l = -1, r = nums.size();while(l+1 < r){int mid = l + r >> 1;if(nums[mid] <= target)l = mid;elser = mid;}//引用l、r前需要判断是否为合法下标if( l == -1 || nums[l] != target) return -1;else return l;}
};

2、在排序数组中查找元素的第⼀个和最后⼀个位置 点击跳转题目

二分两次,分别找到红色、黄色的划分挡板

代码:

class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {int l = -1, r = nums.size();int ret1,ret2;if(r == 0) return {-1,-1};while(l +1 < r){int mid = l + r >> 1;if(nums[mid] < target)l = mid;elser = mid;}if(l+1 >= nums.size() || nums[l+1] != target) return {-1,-1};ret1 = l + 1;l = -1, r = nums.size();while(l +1 < r){int mid = l + r >> 1;if(nums[mid] <= target)l = mid;elser = mid;}ret2 = l;return {ret1,ret2};}
};

3、搜索插⼊位置----点击跳转题目

以小于等于target为标准划分数组,对比l指向的元素是否为target

代码:

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

4、x的平⽅根----点击跳转题目

对0~INT_MAX做二分,挡板左边:平方小于等于x;挡板右边:平方大于x

代码:

class Solution {
public://l: l^2 <= x//r: r^2 > xint mySqrt(int x) {int l = -1, r = INT_MAX;while(l + 1 < r){//做平方时防止溢出long long mid = l + r >> 1;if(mid*mid <= x)l = mid;elser = mid;}if(l==-1) return 0;else return l;}
};

5、⼭峰数组的峰顶---点击跳转题目

以山峰元素的右侧为挡板

挡板左侧:arr[i-1] < arr[i]

挡板右侧:arr[i-1] > arr[i]

代码:

class Solution {
public:int peakIndexInMountainArray(vector<int>& arr) {//l:arr[l] < arr[l+1] //r: arr[r] > arr[r+1] //返回rint l = -1, r = arr.size();while(l + 1 < r){int mid = l + r >> 1;if(arr[mid] < arr[mid+1])l = mid;elser = mid;}return r;}
};

6、寻找峰值----点击跳转题目

观察可知,峰值元素的左侧满足递增、右侧满足递减;由于是返回任意峰值,所以和上一题一样;

挡板左侧:arr[i-1] < arr[i]

挡板右侧:arr[i-1] > arr[i]

代码:

class Solution {
public:int findPeakElement(vector<int>& nums) {int l = -1, r = nums.size();while(l +1 < r){int mid = l + r >> 1;if(mid-1 >= 0){if(nums[mid-1] < nums[mid])l = mid;elser = mid;}else    l = mid;//数组越界时说明在比较nums[-1]和nums[0]}return l;}
};

7、搜索旋转排序数组中的最⼩值----点击跳转题目

由于数组是由单调递增的数组旋转得到,那么得到的数组满足左侧区间永远大于右侧区间

以最小值左侧为挡板,取数组最后一个值x来比较(因为他是第二段区间的最大值)

挡板左侧:左侧元素 > x

挡板右侧:右侧元素 <= x (因为右侧区间可能只有一个元素,所以取等)

代码:

class Solution {
public:int findMin(vector<int>& nums) {int l = -1, r = nums.size();int x = nums[r-1];while(l + 1 < r){int mid = l + r >> 1;if(nums[mid] > x)l = mid;elser = mid;}return nums[r];}
};

8、点名----点击跳转题目

数组中记录的是未缺勤的学生,通过观察我们发现,如果所有学生都未缺勤,那么数组下标就会和学号正好相等,那么在点名还没点到缺勤学生时,这个性质依然成立,以缺勤学生本该在的位置为挡板:

挡板左侧:数组下标 等于 对应元素

挡板右侧:数组下标 不等于 对应元素

代码:

class Solution {
public:int takeAttendance(vector<int>& records) {//l:下标和值相等 r:下标和值不想等int l = -1, r= records.size();while(l + 1 < r){int mid = l + r >> 1;if(records[mid] == mid)l = mid;elser = mid;}return l+1;}
};

通过以上题目的练习我们发现,⼆分的本质:找到⼀个判断标准,使得查找区间能够⼀分为⼆,分界中存在一个挡板,挡板左侧为l,挡板右侧为r


http://www.ppmy.cn/embedded/30225.html

相关文章

2024年Q1季度白酒行业数据分析:消费升级下,白酒均价上涨

前段时间&#xff0c;飞天茅台被曝批发参考价再次下探。而从线上市场的整体情况来看&#xff0c;白酒行业均价同比去年却有所上涨。鲸参谋数据显示&#xff0c;白酒均价在750元左右&#xff0c;同比去年上涨了14%。 尽管白酒行业均价有所上涨&#xff0c;但今年第一季度表现不…

LeetCode 238. 除自身以外数组的乘积

原题链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32…

【Kotlin】select简介

1 前言 协程的 select 是一种用于异步操作的选择器&#xff0c;它允许同时等待多个挂起函数的结果&#xff0c;并在其中一个完成时执行相应的操作。 能够被 select 的事件都是 SelectClause&#xff0c;在 select.kt 中有定义&#xff0c;如下。 public interface SelectBuild…

django设计模式理解FBV和CBV

在 Web 开发中&#xff0c;FBV&#xff08;Function-Based Views&#xff09;和 CBV&#xff08;Class-Based Views&#xff09;是两种常见的视图设计模式&#xff0c;用于处理 HTTP 请求并生成相应的响应。下面是它们的简要解释&#xff1a; Function-Based Views (FBV) 在 …

计算机网络-408考研

后续更新发布在B站账号&#xff1a;谭同学很nice http://【计算机408备考-什么是计算机网络&#xff0c;有什么特点&#xff1f;】 https://www.bilibili.com/video/BV1qZ421J7As/?share_sourcecopy_web&vd_source58c2a80f8de74ae56281305624c60b13http://【计算机408备考…

Android Studio的笔记--布局文件

关于Layout布局文件的使用 LinearLayoutRelativeLayout之前文章的内容一些常见性质在android.graphics.Color中定义了12种常见的颜色常数线性布局LinearLayout 一些常见使用文本框TextView设置文本内容编辑框EditText获取文本内容按钮Button控件使用其他按钮修改图标及名称添加…

SQL数据库

一.什么是数据库 数据库&#xff1a;存储数据的仓库&#xff0c;数据是有组织的进行存储。&#xff08;database 简称DB&#xff09; 数据库管理系统&#xff1a;管理数据库的大型软禁&#xff08;DataBase Management System 简称DBMS&#xff09; SQL&#xff1a;操作关系…

KIE关键信息抽取——SDMG-R

https://arxiv.org/pdf/2103.14470https://arxiv.org/pdf/2103.14470 1.概述 背景:传统的关键信息提取方法依赖于模板匹配,这使它们难以泛化到未见过的模板,且对文本识别错误不够鲁棒。SDMG-R方法:提出一种端到端的双模态图推理方法,通过构建双模态图(视觉和文本特征),…