2023/02/二分答案
目标
学会二分答案的基本模板,并能进行简单应用。
重点
二分答案模板的熟悉及对最优性问题转可行性问题的处理。
导图大纲
回顾
复习二分查找。
回顾下二分查找的思想,若序列呈升序,我们求出中间值mid,并判断是否满足条件。满足条件输出答案,若不满足将正确答案与mid进行大小的判断,如果比mid大,说明答案在右侧,更新查找区间的最小范围;如果比mid小,说明答案在左侧,更新查找区间的最大范围。
二分查找模板:
int binarySearch(int key,int a[]){
int l=MIN,r=MAX;//查找的范围为l~r
int mid;//存储中间值
while(l<=r){
mid=(l+r)/2;//求出中间值
if(key==a[mid]){//中间值等于要找的
return mid;//返回key在数组中的位置
}else if(a[mid]<key){//key比a[mid]大
//缩小查找范围为右侧
l=mid+1;//更新最小范围
}else{//key比a[mid]小
//缩小范围为左侧
r=mid-1;//更新最大范围
}
}
return -1;//值不存在
}
引入
在进行查找的时候,若我们在有序的数列中查找某个符合条件的值,可以采用二叉查找的方式快速的求解内容。二分查找的时间复杂度为
。而对题目做修改,修改成,查找某个符合某个条件的值的最大或最小的值。此时,套用之前的二分查找的模板就不能够方便地去查找它的位置了。
用之前的模板去进行查找,只能找到某个满足条件的值,而在此基础上的找满足条件的最优值的处理,该算法模板便无能为力了。
此时,我们引入二分答案,来解决此类问题。
二分答案类问题抽象
形如这样的问题“求在有序的对象中,满足某个条件C(x)的最小的x”。
有序性的广义化
能用二分答案的问题需要有序,这儿的有序不是传统意义上的值从小到大或者是值从大到小的排列。从数学的角度去理解“有序”,可以理解为:若是升序,那么对任意满足C(x)的x,如果所有的
也满足C(x')。说是降序,那么对任意满足C(x)的x,如果所有的也满足C(x')。
所有满足以上条件的数列对象,可以称之为有序的。
二分答案的模板
- 最小化问题
- 最大化问题
若我们要求“在有序对象中,满足某个条件C(x)的最小的x”。首先,我们将区间的左端点初始化为不满足C(x)的话,右端点初始化为满足C(x)的值(升序)。然后每次取中点
,判 断C(mid)是否满足条件,若满足条件,那么更大的值就不用去找,因为它们都满足条件C(x),缩小范围,在更小的值中寻找是否有更小的满足条件的值;
如果不满足条件,则更小的值就不用找,因为它们都不满足条件C(x),缩小范围,在更大的值中寻找是否有满足条件的值。
最小化模板代码
int lb=MIN,rb=MAX;//确定答案的最小、最大范围。
int ans=-1;//最优值位置
while(lb<=rb){//在范围内进行寻找
int mid=(lb+rb)/2;//求中间值
if(check(mid)){//判断是mid是否满足条件C
ans=mid;//满足条件,则更新结果
rb=mid-1;//寻找满足条件的最小值,故在更小的范围内继续寻找
//缩小寻找范围: lb ~ mid-1
}else{//不满足条件,则在更大的范围内寻找能满足条件的值
lb=mid+1;//缩小寻找范围: mid+1 ~ rb
}
cout<<ans;//输出满足条件的最小的值
}
若我们要求“在有序对象中,满足某个条件C(x)的最大的x”。首先,我们将区间的左端点初始化为满足C(x)的话,右端点初始化为不满足C(x)的值。然后每次取中点,判断C(mid)是否满足条件,若满足条件,那么更小的值就不用去找,因为x比它们都大,我们要缩小范围,在更大的值中去寻找满足条件的值。
若不满足条件,那么右侧更大的值就不用去找,因为都不满足,我们缩小范 围,在左侧更小的之中寻找满足条件的值。
最大化模板代码
int lb=MIN,rb=MAX;//确定答案的最小、最大范围。
int ans=-1;//最优值位置
while(lb<=rb){//在范围内进行寻找
int mid=(lb+rb)/2;//求中间值
if(check(mid)){//判断是mid是否满足条件C
ans=mid;//满足条件,则更新结果
lb=mid+1;//寻找满足条件的最大值,故在更大值的范围内继续寻找
//缩小寻找范围: mid+1 ~ rb
}else{//不满足条件,则在更小值的范围内寻找能满足条件的值
rb=mid-1;//缩小寻找范围: l ~ mid-1
}
cout<<ans;//输出满足条件的最大的值
}