当前位置:网站首页>什么时候运用二分搜索
什么时候运用二分搜索
2022-06-12 12:28:00 【Joey Liao】
在 二分搜索算法框架解析中,详细介绍了二分搜索的细节问题,探讨了「搜索一个元素」,「搜索左侧边界」,「搜索右侧边界」这三个情况,教你如何写出正确无 bug 的二分搜索算法。
但是前文总结的二分搜索代码框架仅仅局限于「在有序数组中搜索指定元素」这个基本场景,具体的算法问题没有这么直接,可能你都很难看出这个问题能够用到二分搜索。
所以本文就来总结一套二分搜索算法运用的框架套路,帮你在遇到二分搜索算法相关的实际问题时,能够有条理地思考分析,步步为营,写出答案。
原始的二分搜索代码
二分搜索的原型就是在「有序数组」中搜索一个元素 target,返回该元素对应的索引。
如果该元素不存在,那可以返回一个什么特殊值,这种细节问题只要微调算法实现就可实现。
还有一个重要的问题,如果「有序数组」中存在多个 target 元素,那么这些元素肯定挨在一起,这里就涉及到算法应该返回最左侧的那个 target 元素的索引还是最右侧的那个 target 元素的索引,也就是所谓的「搜索左侧边界」和「搜索右侧边界」,这个也可以通过微调算法的代码来实现。
**在具体的算法问题中,常用到的是「搜索左侧边界」和「搜索右侧边界」**这两种场景,很少有让你单独「搜索一个元素」。
「搜索左侧边界」的二分搜索算法的具体代码实现如下:
// 搜索左侧边界
int left_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0, right = nums.length;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
// 当找到 target 时,收缩右侧边界
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
return left;
}
假设输入的数组 nums = [1,2,3,3,3,5,7],想搜索的元素 target = 3,那么算法就会返回索引 2。
如果画一个图,就是这样:
「搜索右侧边界」的二分搜索算法的具体代码实现如下:
// 搜索右侧边界
int right_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0, right = nums.length;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
// 当找到 target 时,收缩左侧边界
left = mid + 1;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
return left - 1;
}
输入同上,那么算法就会返回索引 4,如果画一个图,就是这样:
二分搜索问题的泛化
什么问题可以运用二分搜索算法技巧?
首先,你要从题目中抽象出一个自变量 x,一个关于 x 的函数 f(x),以及一个目标值 target。
同时,x, f(x), target 还要满足以下条件:
f(x)必须是在x上的单调函数(单调增单调减都可以)- 题目是让你计算满足约束条件
f(x) == target时的x的值。
运用二分搜索的套路框架
想要运用二分搜索解决具体的算法问题,可以从以下代码框架着手思考:
// 函数 f 是关于自变量 x 的单调函数
int f(int x) {
// ...
}
// 主函数,在 f(x) == target 的约束下求 x 的最值
int solution(int[] nums, int target) {
if (nums.length == 0) return -1;
// 问自己:自变量 x 的最小值是多少?
int left = ...;
// 问自己:自变量 x 的最大值是多少?
int right = ... + 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (f(mid) == target) {
// 问自己:题目是求左边界还是右边界?
// ...
} else if (f(mid) < target) {
// 问自己:怎么让 f(x) 大一点?
// ...
} else if (f(mid) > target) {
// 问自己:怎么让 f(x) 小一点?
// ...
}
}
return left;
}
具体来说,想要用二分搜索算法解决问题,分为以下几步:
- 确定
x, f(x), target分别是什么,并写出函数f的代码。 - 找到
x的取值范围作为二分搜索的搜索区间,初始化left和right变量。 - 根据题目的要求,确定应该使用搜索左侧还是搜索右侧的二分搜索算法,写出解法代码。
例题一:爱吃香蕉的珂珂
这是力扣第875题「爱吃香蕉的珂珂」:

确定
x,f(x), target分别是什么,并写出函数f的代码。自变量
x是什么呢?回忆之前的函数图像,二分搜索的本质就是在搜索自变量。
所以,题目让求什么,就把什么设为自变量,珂珂吃香蕉的速度就是自变量×。那么,在
x上单调的函数关系f(x)是什么?
显然,吃香蕉的速度越快,吃完所有香蕉堆所需的时间就越少,速度和时间就是一个单调函数关系。所以,f(x)函数就可以这样定义∶若吃香蕉的速度为x根/小时,则需要f(x)小时吃完所有香蕉。
//速度为k时,需要f(k)才能吃完所有香蕉
//f(k)随着k单调递减
public int f(int piles,int k){
int res=0;
for(int pile:piles){
res+=(pile/k+pile%k==0?0:1);
}
return res;
}
target自然就是吃香蕉的时间限制,是对f(x)返回值的最大约束
- 找到
x的取值范围作为二分搜索的搜索区间,初始化left和right变量。
显然吃香蕉的最小速度为1,最大速度为piles数组中的最大值 - 根据题目的要求,确定应该使用搜索左侧还是搜索右侧的二分搜索算法,写出解法代码。
现在我们确定了自变量x是吃香蕉的速度,f(x)是单调递减的函数,target就是吃香蕉的时间限制H,题目要我们计算最小速度,也就是x要尽可能小:
这就是搜索左侧边界的二分搜索嘛,不过注意f(x)是单调递减的,不要闭眼睛套框架,需要结合上图进行思考,写出代码︰
class Solution {
public int minEatingSpeed(int[] piles, int h) {
int left=1,right=1000000000+1;
while(left<right){
int mid=left+(right-left)/2;
if(f(piles,mid)==h){
//搜索左侧边界,则需要收缩右侧边界
right=mid;
}else if(f(piles,mid)<h){
//需要让f(x)的返回值大一点
right=mid;
}else{
//需要让f(x)的返回值小一点
left=mid+1;
}
}
return left;
}
public int f(int[] piles,int k){
int res=0;
for(int pile:piles){
res+=pile/k;
res+=pile%k==0?0:1;
}
return res;
}
}
例题二:运送货物
再看看力扣第1011 题「在D天内送达包裹的能力」:

- 确定
x, f(x), target分别是什么,并写出函数f的代码。 - 找到
x的取值范围作为二分搜索的搜索区间,初始化left和right变量。 - 根据题目的要求,确定应该使用搜索左侧还是搜索右侧的二分搜索算法,写出解法代码。
class Solution {
public int shipWithinDays(int[] weights, int days) {
int left=0,right=1;
for(int weight:weights){
left=Math.max(left,weight);
right+=weight;
}
while(left<right){
int mid=left+(right-left)/2;
if(f(weights,mid)<=days){
//搜索左侧边界,则需要收缩右侧边界
right=mid;
}else{
//需要让f(x)的返回值小一点
left=mid+1;
}
}
return left;
}
//x为运载能力
//f(x)为运完的时间
public int f(int[] weights,int x){
int cnt=0,res=1;
for(int weight:weights){
if(cnt>=weight){
cnt-=weight;
}else{
cnt=x-weight;
res++;
}
}
return res;
}
}
例题三:分割数组的最大值
我们实操一下力扣第410题「分割数组的最大值」,难度为困难:
哈哈哈哈哈,发现和第二道题是一模一样的
边栏推荐
- 2021-11-16
- Win7 registers out of process components, services, and COM component debugging
- Iterator, generator generator details
- NewOJ Week 10题解
- 银行布局元宇宙:数字藏品、数字员工成主赛道!
- The direction of this
- itk 多分辨率图像 itk::RecursiveMultiResolutionPyramidImageFilter
- vtk 三视图
- Object value taking method in JS And []
- 【数据库】navicat --oracle数据库创建
猜你喜欢

Rust语言学习

二叉树(纲领篇)

InfluxDB2.x 基准测试工具 - influxdb-comparisons

时序数据库 - InfluxDB2 docker 安装

itk itk::BSplineDeformableTransform

this.$ How to solve the problem when refs is undefined?

点云配准--gicp原理与其在pcl中的使用

JS attribute operation and node operation

Async/await for ES6

Introduction, installation and use of core JS
随机推荐
关系代数笛卡尔积和自然连接的例子
ITK Examples/RegistrationITKv4/DeformableRegistration
【您编码,我修复】WhiteSource正式更名为Mend
[译] Go References - The Go Memory Model | golang官方文档中文翻译之内存模型
MySQL review
牛顿法解多项式的根
【VIM】.vimrc配置,已经安装Vundle,YoucompleteMe
JS how to convert a string into an array object
Advanced C language -- storage of deep anatomical data in memory (with exercise)
itk::SymmetricForcesDemonsRegistrationFilter
Imx6 uboot add lvds1 display
Introduction and test of MySQL partition table
LDAP和SSO集成能实现什么效果?
Native JS implements the copy text function
Point cloud registration -- GICP principle and its application in PCL
Boot entry directory
Introduction, installation and use of core JS
[C language] keyword static & Multi file & guessing game
TRON-api-波场转账查询接口-PHP版本-基于ThinkPHP5封装-附带接口文档-20220528版本
你不会只会用console.log()吧?