当前位置: 首页 > news >正文

【算法笔记】二分查找与二分答案

前言:二分法主要用于快速的查找某个数,当这段区间满足二段性时我们就可以使用这个方法来以O(log n)的时间复杂度来完成目的。二分的模板有好几个,但我这里就固定使用一个模板来解决问题了,二分的模板也不难难度主要体现在题目里奇奇怪怪的细节问题


1.二分的概念及其模板

当一个区间可以被某个条件分为左右两个区间时,我们就称之为具有二段性。假如我有一个非严格递增的区间,我要找到下标最小的数字3,这时我就可以先定义定义两个指针,一个为指向区间左端点的指针left,一个为指向区间右端点的指针right 。这时我们可以将这个区间分别两个区间,一个区间的数都是小于3的,另一个大于等于3:

此时我们定义一个中间变量mid:

int mid = (left + right + 1) / 2;//第一种 int mid = (left + right) / 2;//第二种

到底用哪种我会在介绍模板时有说明,这个mid的作用是更新区间,用于舍去不可能出现目标值的区间。

比如当mid落左区间,左区间因为都比3小所以不可能存在目标值所以这个时候我们就更新左指针left = mid + 1;当mid落在右区间就更新右指针right为 right = mid; 当程序不再满足left < right时就结束,此时左指针就指向了我们的结果3;

#include <iostream> using namespace std; int a[] = {0, 1, 1, 2, 3, 3, 4, 5}; int main() { int left = 1, right = 7; while (left < right) { int mid = (left + right) / 2; if (a[mid] < 3) left = mid + 1; else right = mid; } cout << a[left] << endl; return 0; }

模板

这里你可以会有疑问,mid到底要不要加一,left < right 为什么不可以写成 left <= right 。这是因为在一些特定的情况下会出现死循环的结果,但是一个个慢慢的推又太慢了没必要,所以我这里就统一使用一个模板了:

循环的条件都统一为 left < right

当if / else 没有减1时,mid就不需要加1:

int l = 1, r = n; while(l < r) { int mid = (l + r) / 2; if(check(mid)) r = mid; else l = mid + 1; }

当if / else 出现了减1时,mid就需要加1:

int l = 1, r = n; while(l < r) { int mid = (l + r + 1) / 2; if(check(mid)) l = mid; else r = mid - 1; }

这个check就是二分的条件,只是上面的二分查找条件是大于小于某个数而已,使用比较的狭隘这点我后面介绍二分答案就可以明白了

其实在C++的SLT中本来就为我们准备好了现成的二分模板,但是只能有序的数组中使用,二分答案就用不了了,所以还是需要学习上面的二分模板。

这个模板包含在头文件:

#include <algorithm>

中,返回的是迭代器因此我们还需要对返回值解引用。这里我就简单的说一下这个模板的使用就不重复的写代码了:

lower_bound //返回⼤于等于 x 的最⼩元素,时间复杂度O(log N) upper_bound //返回⼤于x的最⼩元素,时间复杂度O(log N)

2.二分查找经典例题

2.1牛可乐和魔法封印

这道题就是二分查找的经典应用了,我们需要二分两次分别把左、右区间找出来就可以了,这就是经典的套模板了,如何害怕mid溢出的话可以用:

mid = l + (r - l) / 2

或者直接开一个long long 比较省事

#include <iostream> using namespace std; const int N = 1e5 + 10; typedef long long LL; int n, q; LL a[N]; int main() { cin >> n; for (int i = 1; i <= n; i++) cin >> a[i]; cin >> q; while (q--) { LL k1, k2; cin >> k1 >> k2; LL left = 1, right = n; while (right > left) { LL mid = (left + right) / 2; if (a[mid] >= k1) right = mid; else left = mid + 1; } if (a[right] < k1) { cout << 0 << endl; continue; } LL retright = right; left = 1, right = n; while (right > left) { LL mid = (left + right + 1) / 2; if (a[mid] <= k2) left = mid; else right = mid - 1; } if (a[left] > k2) { cout << 0 << endl; continue; } cout << left - retright + 1 << endl; } return 0; }

2.2在排序数组中查找元素的第⼀个和最后⼀个位置

同样的模板题,注意处理一下边界条件比如大小为空、找不到的情况下就可以了:

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

2.3A-B 数对

这道题最好的写法其实是用哈希表来做,但是我们也可以转换一下思路尝试一下使用二分去解决这道问题。因为 A - B = C, 所以可以得出 A = B + C; 所以我们只要枚举一下A, 看看有多少个B满足条件就可以了:

#include <iostream> #include <algorithm> using namespace std; typedef long long LL; const int N = 2e5 + 10; LL n, c; LL a[N]; int main() { cin >> n >> c; for (int i = 1; i <= n; i++) cin >> a[i]; sort(a + 1, a + n + 1); LL ret = 0; for (int i = 1; i <= n; i++) { LL b = a[i] - c; ret += upper_bound(a + 1, a + i, b) - lower_bound(a + 1, a + i, b); } cout << ret << endl; return 0; }

2.4烦恼的高考志愿

因为我们需要找出最接近高考分数的分数线,所以我们可以先找出大于等于高考分数的位置pos,那这个最接近的分数要么是pos要么就是pos - 1的位置,我们在这两个位置找一个分数线减去高考分数绝对值最小的就可以了:

#include <iostream> #include <algorithm> #include <cmath> using namespace std; typedef long long LL; const int N = 1e5 + 10; int a[N]; int b[N]; int m, n; int main() { cin >> m >> n; for (int i = 1; i <= m; i++) scanf("%ld", &a[i]); sort(a + 1, a + m + 1); LL ret = 0; int len = n; while (len--) { int x; scanf("%d", &x); LL left = 1, right = m; LL mid; while (left < right) { mid = (left + right + 1) / 2; if (a[mid] <= x) left = mid; else right = mid - 1; } int tem = left; left = 1, right = m; while (left < right) { mid = (left + right) / 2; if (a[mid] >= x) right = mid; else left = mid + 1; } int sz = min(abs(x - a[tem]), abs(x - a[left])); ret += sz; } cout << ret << endl; return 0; }

3.二分答案

二分答案是在二分查找的基础上增加了对二分条件考察难度,这也是该类题目最难的地方因为确实是挺难想的。二分答案一般都是用来解决最小值最大、最大值最小的问题。因为题目的解集中有可能在又小变大的变化过程中出现二段性,下面我将以几个题目来举例

3.1木材加工

这次我们将切割的长度由小到大变成一个区间按,很明显根据题意长度越小切割的段数就越多,很明显存在个一个位置ret可以在满足段数的情况下切割出最大长度:

我们只需要另外写一个函数将段数转化成长度就可以了:

#include <iostream> using namespace std; const int N = 1e5 + 10; typedef long long LL; LL n, k; LL a[N]; int chle(int x) { LL ret = 0; for (int i = 1; i <= n; i++) { ret += a[i] / x; } return ret; } int main() { cin >> n >> k; for (int i = 1; i <= n; i++) cin >> a[i]; LL left = 0, right = 1e8; while (left < right) { int mid = (left + right + 1) / 2; if (chle(mid) >= k) left = mid; else right = mid - 1; } cout << left << endl; return 0; }

3.2砍树

这道题与上道题几乎一摸一样我这里就直接贴代码了:

#include <iostream> using namespace std; const int N = 1e6 + 10; typedef long long LL; LL a[N]; LL m, n; LL chie(LL x) { LL ret = 0; for (int i = 1; i <= n; i++) { if (a[i] > x) ret += (a[i] - x); } return ret; } int main() { cin >> n >> m; for (int i = 1; i <= n; i++) cin >> a[i]; LL left = 1, right = 2e9; while (left < right) { LL mid = (left + right + 1) / 2; if (chie(mid) >= m) left = mid; else right = mid - 1; } cout << left << endl; return 0; }

http://www.jsqmd.com/news/699768/

相关文章:

  • 解决DWPose预处理器ONNX运行时错误的深度技术分析与修复方案
  • 集团总部失控:诸侯是怎么养成的?
  • 为什么 Agent 框架越来越多:LangChain、LangGraph、AutoGen 生态对比
  • 【嵌入式调试新纪元】:VSCode 2026原生支持SWD over USB-C、内存映射热重载与双核同步断点(仅限首批127个MCU型号)
  • Cursor Pro激活器实战:3步高效破解AI编程助手限制
  • Materials Project API技术架构与高级应用指南:从数据查询到材料科学创新
  • stp思维导图
  • k1周:多模态融合-阿尔茨海默病检测
  • 剪映专业版教程:制作百叶窗转场效果
  • 从 Agent 到 Agentic AI:企业级智能体工程实现的关键差异
  • 显卡驱动彻底清理指南:Display Driver Uninstaller深度解析与实战应用
  • Docker 与 Kubernetes 部署最佳实践 2027
  • UnityFigmaBridge:打破设计与开发壁垒的终极协作解决方案
  • AI 伴侣的伦理困境:当代码学会说「我爱你」,人类准备好了吗?
  • 为什么92%的嵌入式团队在LLM移植中踩坑?:揭秘C语言指针对齐陷阱、中断上下文推理崩溃、Flash页擦写冲突三大“静默杀手”
  • AI Agent在体育与娱乐领域的应用:数据分析与体验优化
  • 如何快速解密Wii U游戏文件:CDecrypt工具完整指南 [特殊字符]
  • Python快速验证分类算法:scikit-learn实战指南
  • BilibiliDown:跨平台B站视频下载的完整解决方案
  • Claude-Code-Workflow:基于AI的智能研发工作流引擎实战解析
  • 嵌入式团队紧急升级预警:VSCode 2026.1起废弃legacy GDB adapter——3类老旧JTAG探针将彻底失联?
  • 卡梅德生物技术快报|哺乳动物细胞表达系统:载体优化、宿主选型与位点重组技术实现方案
  • 第5章:时间的相对性思辨
  • Windows上使用VS2026和CMake编译LearnOpenGL项目源代码
  • 深入解析 Ansible:从入门到实践
  • 如何快速搭建全平台直播弹幕抓取系统:终极实战指南
  • 解密ClickShow:Windows鼠标交互的视觉化革命
  • 2026攻防实战:如何利用AI工作流实现自动化WAF绕过与Payload变异?
  • 结构化输出与函数调用:智能代理系统设计核心解析
  • HNU计算机系统期中题库详解(五)位运算与逻辑运算