二分算法详细解析

article/2025/9/19 13:03:09

二分

  • 有单调性一定可以二分,可以二分不一定有单调性
  • 二分的本质是边界不是单调性(单调一定可以二分,不单调的有的也可以二分)

本质:在一个区间上,找到某种性质,每次可以将区间一分为二(存在边界),一个区间满足、另外一个区间不满足,答案就在边界上!

整数二分的两种情况:

在这里插入图片描述
两种模板的答案分别对应红、绿边界点(图上箭头所示)

一般步骤(简略版):
1.确定一个区间,使得目标值一定在区间中。
2.找一个性质,满足:
(1)性质具有二段性(即可以分为两个区间)
(2)答案是二段性的分界点
最终结果:
循环结束后,l=r,答案就是上图我们所画的边界。
总结:
根据某个性质将目标值转化为二段性上的分界点(分界点就是答案),具体来讲就是根据某个性质,这个性质使区间具有二段性,使答案在分界点上。

模板一:

bool check(int x) {/* ... */} // 检查x是否满足某种性质// 区间[l, r]被划分成[l, mid][mid + 1, r]时使用:
int bsearch_1(int l, int r)
{while (l < r){int mid = l + r >> 1;if (check(mid)) r = mid;    // check()判断mid是否满足性质else l = mid + 1;}return l;
}

解释:
模板一对应上图绿色线段边界,属于>=target的第一个元素

模板二:

bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid - 1][mid, r]时使用:
int bsearch_2(int l, int r)
{while (l < r){int mid = l + r + 1 >> 1;if (check(mid)) l = mid;else r = mid - 1;}return l;
}

解释:
模板一对应上图红色线段边界,属于<=target的最后一个元素

总结:

在这里插入图片描述

  • lower_bound寻找>=x的第一个数,应该说lower_bound对应二分第一个模板,以此类推。
  • 两个模板的区别就在于如何划分区间,以及mid的运算是否需要+1

特别注意:
如果查的数没有,则模板一会找到比target大的第一个数,模板二会找比target小的最后一个数。

整数二分详细步骤:

  1. 找一个区间[L,R],使得答案一定在该区间中;
  2. 找一个判断条件,使得该判断条件具有二段性,并且答案一定是该二段性的分界点;
  3. 分析中点mid在该判断条件下是否成立,如果成立,考虑答案在哪个区间;如果不成立,考虑答案在哪个区间;
  4. 如果更新方式写的是r=mid,则不用做任何处理;如果更新方式写的是l=mid,则需要在计算mid时加上1(否则会造成死循环);

分析算法

说明:
我们根据某种性质,将目标值转化为相关边界点(红或绿的其中一个),此时根据二段性,区间已经分成两个不同的区间,一个满足性质,另外一个不满足性质,且一般边界点在满足性质的区间上。
在这里插入图片描述
对于绿线段情况(前提条件:答案在绿线段分界点,箭头所指位置,左端点),这里我们假设checked函数返回true时在答案所在段

如果checked(mid) 为 true(即满足某种性质),即mid所在位置为绿线段内(因为根据二段性,此时区间已经分为红线段和绿线段两个区间),则答案(ans)一定在[L,M] 之间(因为需要让ans在绿线段边界,左端点)M也可能为答案,否则在 [M+1,R]之间,如果为true,在绿线段之间,则下次循环r=mid,否则l=mid+1,以此类推。

对于红线段情况((前提条件:答案在红线段右端点,箭头所指位置),这里我们假设checked函数返回true时在答案所在段

如果checked(mid) 为 true(即满足某种性质),即mid所在位置为红线段内(因为根据二段性,此时区间已经分为红线段和绿线段两个区间),则答案(ans)一定在[M,R] 之间(因为需要让ans在红线段边界,右端点)M也可能为答案,否则在 [L,M-1]之间,如果为true,在绿线段之间,则下次循环l=mid,否则r=mid-1,以此类推。(注意此时算mid时需要额外+1防止死循环!)。

上述如果、否则对应代码中if、else。

例题1:

给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。

对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。

如果数组中不存在该元素,则返回-1 -1

输入格式
第一行包含整数 n 和 q,表示数组长度和询问个数。

第二行包含 n 个整数(均在 1∼10000 范围内),表示完整数组。

接下来 q 行,每行包含一个整数 k,表示一个询问元素。

输出格式
共 q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。

如果数组中不存在该元素,则返回-1 -1

数据范围
1≤n≤100000
1≤q≤10000
1≤k≤10000
输入样例:

6 3
1 2 2 3 3 4
3
4
5

输出样例:

3 4
5 5
-1 -1

时/空限制:1s / 64MB
来源:AcWing


#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>using namespace std;const int N = 100010;int n, m;
int q[N];int main()
{scanf("%d%d", &n, &m);for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);for (int i = 0; i < m; i ++ ){int x;scanf("%d", &x);// 二分x的左端点,绿线段情况int l = 0, r = n - 1;   // 确定区间范围while (l < r){int mid = l + r >> 1;if (q[mid] >= x) r = mid;//q[mid]>=x即为性质,将区间一分为二,将目标值转化为分界点,因为只有含目标值的区间满足此性质,不含目标值的区间不满足此性质,且满足此性质的区间分界点就是我们的答案!else l = mid + 1;}if (q[r] == x){cout << r << ' ';// 二分x的右端点r = n - 1;  // 右端点一定在[左端点, n - 1] 之间while (l < r){int mid = l + r + 1 >> 1;   // 因为写的是l = mid,所以需要补上1if (q[mid] <= x) l = mid;else r = mid - 1;}cout << r << endl;}else cout << "-1 -1" << endl;}return 0;
}

例题2:(没有单调性的二分说明)

不修改数组找出重复的数字

给定一个长度为 n+1 的数组nums,数组中所有的数均在 1∼n 的范围内,其中 n≥1。

请找出数组中任意一个重复的数,但不能修改输入的数组。

样例
给定 nums = [2, 3, 5, 4, 3, 2, 6, 7]。
返回 2 或 3。

思考题:如果只能使用 O(1) 的额外空间,该怎么做呢?

class Solution {
public:int duplicateInArray(vector<int>& nums) {int l=1,r=nums.size()-1;//此时的区间是1-n的数,不是nums数组!while(l<r){int mid=(l+r)/2;int cnt=0;for(int i=0;i<nums.size()-1;i++){if(nums[i]<=mid)  cnt++;}if(cnt>mid) r=mid;#说明前半个区间中有重复的else l=mid+1;}return l;}};

解析:
(分治,抽屉原理) O(nlogn)O(nlogn)
这道题目主要应用了抽屉原理和分治的思想。

抽屉原理:n+1 个苹果放在 n 个抽屉里,那么至少有一个抽屉中会放两个苹果。

用在这个题目中就是,一共有 n+1 个数,每个数的取值范围是1到n,所以至少会有一个数出现两次。

然后我们采用分治的思想,将每个数的取值的区间[1, n]划分成[1, n/2]和[n/2+1, n]两个子区间,然后分别统计两个区间中数的个数。
注意这里的区间是指 数的取值范围,而不是 数组下标。

划分之后,左右两个区间里一定至少存在一个区间,区间中数的个数大于区间长度。
这个可以用反证法来说明:如果两个区间中数的个数都小于等于区间长度,那么整个区间中数的个数就小于等于n,和有n+1个数矛盾。

因此我们可以把问题划归到左右两个子区间中的一个,而且由于区间中数的个数大于区间长度,根据抽屉原理,在这个子区间中一定存在某个数出现了两次。

依次类推,每次我们可以把区间长度缩小一半,直到区间长度为1时,我们就找到了答案。

浮点数二分(只有一种情况)

模板如下:

bool check(double x) {/* ... */} // 检查x是否满足某种性质double bsearch_3(double l, double r)
{const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求while (r - l > eps){double mid = (l + r) / 2;if (check(mid)) r = mid;else l = mid;}return l;
}

例题:
给定一个浮点数 n,求它的三次方根。

输入格式
共一行,包含一个浮点数 n。

输出格式
共一行,包含一个浮点数,表示问题的解。

注意,结果保留 6 位小数。

数据范围
−10000≤n≤10000
输入样例:

1000.00

输出样例:

10.000000

代码:

#include <iostream>
#include <algorithm>using namespace std;int main()
{double x;cin >> x;double l = -10000, r = 10000;while (r - l > 1e-8)#一般比题目要求精度再往后两位,更保险{double mid = (l + r) / 2;if (mid * mid * mid >= x) r = mid;else l = mid;}printf("%lf\n", l);return 0;
}

总结

在这里我们举个例子:
我们需要查找下图红色字体6的位置,也就是值为6的第一个数,及>=target(6)的第一个数,我们就可以使用二分算法。
在这里插入图片描述
为什么可以使用二分?因为具有二段性,A段的值都小于6,B段的值都>=6,且答案就在B段的边界上
在这里插入图片描述
上图情况对应本文二分的第一个模板在这里插入图片描述
如何写算法呢?
此时我们已经找到了某种性质(具体就是>=6的在一个段,<6的在一个段(每一个段中的所有数据都满足此段的性质),不具体的就是>=target的数据在一个段,<target的数据在一个段),可以将该区间分为二段,checked函数的书写就是根据性质来定的,但是checked函数必须满足其中一个段的性质(比如此题中性质分别是>=6、<6,那么checked函数需要是其中一个),比如>=target返回true,或者<target返回true。
因为性质可以将区间分成两段,checked函数需要满足其中一段的性质,所以checked有两种写法,对应的if和else语句就有两种写法(其实就是两种情况的if和else语句内容交换了而已)。
过程:
1.根据任务(查找>=target的第一个数)确定性质(A段都<target,B段都>=target),将区间分成两段,且目标值就是其中一段的边界点(易知在B段的边界点上)
在这里插入图片描述
2.由上图可以确定使用的二分模板为模板一
3.checked函数
4.套用模板一
代码举例:
1.当checked函数返回true的性质在B段时

#include<iostream>
#include<vector>
using namespace std;
bool checked(int x){if(x>=6) return true;else return false;
}
int main()
{vector<int> v={1,2,3,4,5,6,6,6,7,8,9,10};int l=0,r=v.size()-1;while(l<r){int mid=(l + r) >> 1;//根据本次checked代码可以看出,当checked函数返回true时,说明在B段(答案所在的段)//为了使范围进一步缩小且越来越接近答案(B段边界点),需要使r=midif(checked(v[mid])) r=mid;else l = mid+1;}cout<<l;return 0;
}

2.当checked函数返回true的性质在A段(非答案所在段)时

#include<iostream>
#include<vector>
using namespace std;
bool checked(int x){if(x<6) return true;else return false;
}
int main()
{vector<int> v={1,2,3,4,5,6,6,6,7,8,9,10};int l=0,r=v.size()-1;while(l<r){int mid=(l + r) >> 1;//根据本次checked代码可以看出,当checked函数返回true时,说明在A段(非答案所在段)//为了使范围进一步缩小且越来越接近答案(B段边界点),需要使l=mid+1if(checked(v[mid])) l = mid+1;else r=mid;}cout<<l;return 0;
}

顺序:
1.找到一个性质将区间分成两段,此时根据性质已经可以确定答案在哪个段上的分界点了。换句话说就是我们根据想找的答案来确定某种性质,从而使其满足二分的要求,同时也确定了答案的位置(某一段的边界点),在写代码前就已经确定了。
2.选择对应的二分模板,写checked函数,根据checked函数的书写不同,会有两种情况,每种情况if和else的代码也不同,两种情况无非就是if和else代码互换一下。
3.注意选择的模板在计算mid时是否需要额外+1.

举第二个例子:

我们需要查找下图红色字体6的位置,也就是值为6的最后一个数,及<=target(6)的最后一个数,我们就可以使用二分算法。
在这里插入图片描述
A段都<=6(性质),B段都>6(性质),我们根据这两个性质将区间分成了两段A段和B段,且答案就在A段的边界点(写代码前就已经判断出了答案在A段的边界点)
对应本文模板二
在这里插入图片描述
写代码:
1.checked(check)函数满足A段(<=6)时返回true

#include<iostream>
#include<vector>
using namespace std;
bool checked(int x){if(x<=6) return true;else return false;
}
int main()
{vector<int> v={1,2,3,4,5,6,6,6,7,8,9,10};int l=0,r=v.size()-1;while(l<r){int mid=(l + r + 1) >> 1;//根据本次checked代码可以看出,当checked函数返回true时,说明在A段(答案所在段)//为了使范围进一步缩小且越来越接近答案(A段分界点),需要使l=mid,否则r=mid-1;if(checked(v[mid])) l = mid;else r=mid-1;}cout<<l;return 0;
}

2.checked(check)函数满足B段(>6)时返回true

#include<iostream>
#include<vector>
using namespace std;
bool checked(int x){if(x>6) return true;else return false;
}
int main()
{vector<int> v={1,2,3,4,5,6,6,6,7,8,9,10};int l=0,r=v.size()-1;while(l<r){int mid=(l + r + 1) >> 1;//根据本次checked代码可以看出,当checked函数返回true时,说明在B段(非答案所在段)//为了使范围进一步缩小且越来越接近答案(A段分界点),需要使r=mid-1,否则l=mid;if(checked(v[mid])) r=mid-1;else l=mid;}cout<<l;//退出条件就是l=r,所以此时输出l和r都可以return 0;
}

http://chatgpt.dhexx.cn/article/Aiw0Bid0.shtml

相关文章

一篇文章带你搞懂二分

一篇文章教你搞懂二分 二分整数二分实数域上二分二分查找二分答案 二分 到底什么是二分呢&#xff1f;二分二分就是一分为二。简单来说二分就是在有序序列中&#xff0c;通过不断的二分&#xff0c;进而不断地缩小范围去寻找满足我们条件的解。这只是对二分一个狭义上的理解&a…

深入字节版atop: 线上系统的性能监控实践

背景 atop 是一款开源的单机性能监测工具&#xff0c;支持实时观测的同时、也支持读取历史文件排查问题。另外一个优点是除提供 CPU、MEM、DISK 等全局指标外&#xff0c;还提供进程、线程级别的各项指标监控数据。鉴于 atop 的这些优点&#xff0c;字节跳动基于社区的 atop 进…

atop备忘

atop 安装 sudo apt install atop atop 参数说明 监控界面字段说明 ATOP列&#xff1a;该列显示了主机名、信息采样日期和时间点 PRC列&#xff1a;该列显示进程整体运行情况 sys、usr字段分别指示进程在内核态和用户态的运行时间 #proc字段指示进程总数 #zombie字段指示僵…

atop linux,每天学一个 Linux 命令(117):atop

命令简介 atop 命令是一款监控 Linux 系统资源与进程的工具&#xff0c;非内部命令&#xff0c;需要安装。[rootcentos7 ~]# atop -bash: atop: command not found [rootcentos7 ~]# yum install atop -y #Debian && Ubuntu apt-get install atop #Fedora dnf install …

linux 系统性能 检测 命令 atop

atop 主要可以 监测 cpu,内存&#xff0c;硬盘&#xff0c;网络性能&#xff0c;以及 实时读写 日志。 基本用法 1.查看 atop , 实时刷新 按 a ,退出 按q 2.保存日志 atop -w log.txt 3.查看 输出日志 atop -r log.txt &#xff0c; 按 t 查看 下一个样本的性能数据 &a…

Atop使用场景

问题&#xff1a; 1、线上容器环境pod报错无法创建本地线程&#xff0c;如下图所示&#xff1a; 2、日志中出现报错信息 “fork&#xff1a;Cannot allocate memory”。如下图所示&#xff1a; 可能原因 1、内存不够 2、可能是进程数超限导致。系统内部的总进程数达到了 p…

atop工具使用

atop是linux系统下一款监控系统资源与进程的工具。atop以一定的频率记录系统的运行状态,通过它可以了解系统今生前世。atop采集系统资源「cpu、mem、disk、net」的使用情况及所有进程使用系统资源的情况,定期采集相关数据保存日志文件。可以通过atop.daily文件调整数据采集的…

atop安装和使用

atop就是一款用于监控Linux系统资源与进程的工具&#xff0c;它以一定的频率记录系统的运行状态&#xff0c;所采集的数据包含系统资源(CPU、内存、磁盘和网络)使用情况和进程运行情况&#xff0c;并能以日志文件的方式保存在磁盘中。atop是一款开源软件&#xff0c;目前最新版…

linux 之atop 系统监控工具

一、atop介绍 atop是一款用于监控Linux系统资源与进程的工具&#xff0c;它以一定的频率记录系统的运行状态&#xff0c;所采集的数据包含系统资源(CPU、内存、磁盘和网络)使用情况和进程运行情况&#xff0c;并能以日志文件的方式保存在磁盘中&#xff0c;服务器出现问题后&a…

Linux 使用 atop 监控工具

文章目录 应用场景操作步骤安装 atop配置并启动 atop编辑atop配置文件启动atop 分析atop停止atop 应用场景 atop 是一款用于监控 Linux 系统资源与进程的工具&#xff0c;以一定的频率记录系统的运行状态&#xff0c;采集系统资源&#xff08;CPU、内存、磁盘和网络&#xff0…

codeblocks中文包

kdevelop因为python 2.6.2 而不是要求的<2.6的缘故不可以显示出automake。 而删除&#xff12;.6的结果是&#xff11;&#xff13;&#xff12;个包同时要卸载&#xff0c;感觉其中不乏kde桌面的东东&#xff0c;遂放弃&#xff0c;转而投向codeblocks怀抱。 /usr/share/c…

codeblocks使用

原文来源&#xff1a;http://www.cnblogs.com/hackergodness/archive/2011/05/26/2059268.html 原手册下载&#xff1a; http://www.codeblocks.org/docs/manual_en.pdf 译者&#xff1a;JGood(http://blog.csdn.net/Jgood ) 译者言&#xff1a;工欲善其事&#xff0c;必先利其…

CodeBlocks安装及使用

Code::Blocks是一个开放源码的全功能的跨平台C/C集成开发环境&#xff0c;由纯碎的C语言开发完成&#xff0c;使用了著名的图形界面库wxWidgets。由于最近上机指导&#xff0c;学校ACM推荐同学们用的是这款软件&#xff0c;故而对其安装及使用进行一些记录。 一、Code::Blocks…

Code::Blocks介绍

软件类型&#xff1a;编程软件 软件性质&#xff1a;免费软件 操作系统&#xff1a;veket 应用平台&#xff1a;veket全系列 网站链接&#xff1a;http://www.codeblocks.org Code::Blocks 是一个开放源码的全功能的跨平台C/C集成开发环境.   相比于基于Delphi的Dev-C共享 CI…

Codeblock汉化教程

#Codeblock汉化教程 我猜许多人和我一样每次拿到一个IDE都不太懂上边的英文是什么意思&#xff0c;而且现在大多数的IDE都没有汉化用起来感觉很麻烦&#xff0c;可能是我用的太少了。最近在练习一些C的东西觉得Codeblock这个IDE用起来比较方便&#xff0c;但是满篇的英文看的我…

codeblock图形界面编程(十)文件操作

目录 codeblock图形界面编程&#xff08;十&#xff09;文件操作C语言的文件管理文件指针文件打开 文件关闭文件读文件写 图形界面的文件操作管理配置libcomdlg32.a库系统功能的实现 codeblock图形界面编程&#xff08;十&#xff09;文件操作 打开我们的计算机&#xff0c;无…

codeblocks应用之debug .

一个偶然的机会&#xff0c;我发现了codeblocks这款IDE&#xff0c;因为它主要用于开发c/c&#xff0c;所以没有visual studio那么臃肿&#xff0c;感觉比较快捷&#xff0c;好用。但是其资料多为英文&#xff0c;本着利人利己的初衷&#xff0c;我决定挑选一些开发中必须使用的…

codeblock报错解决及正确安装

文章目录 前言报错解决原因一原因二 正确安装 前言 文章同步于我的个人博客https://quan9i.github.io/c/#more&#xff0c;欢迎大家访问&#xff01; 最近在重新使用codeblock时发现了部分问题&#xff0c;总结如下 报错解决 在运行时出现如下报错 "HelloWorld - Debu…

codeblocks 使用汇总

享受Code::Blocks编辑快感的几个关键 原文地址&#xff1a;http://blog.csdn.net/Utensil/archive/2008/12/24/3593502.aspx 感谢Loaden的补充。此文是对帖子http://wxforum.shadonet.com/viewtopic.php?t22128 的总结和整理&#xff0c;按个人喜好做了取舍和重新排序。 说明…

CodeBlocks 的下载安装

一、下载 1.在浏览器上输入CodeBlocks或者输入网址https://www.codeblocks.org/&#xff0c;进入CodeBlocks官网 2.点击Downloads&#xff0c;进入Downloads界面 3.进入Downloads页面&#xff0c;点击Download the binary release&#xff08;二进制版&#xff09; 4.选择下…