整数二分:
本文概述:
- 文章以整数二分为背景,介绍了什么是整数二分;并通过具体示例介绍了二分的两个模板,同时指出二分的死循环难点怎么去避免。最后以模板题为例,给出了关键思路与完整代码。
什么是整数二分:
-
有单调性则可二分,但可二分不一定有单调性
-
对区间
[ l , r ]内元素,存在性质check()使区间一分为二,左边元素不满足check(),右边元素满足;那么使用整数二分即可寻找红色区间右边界,或绿色区间左边界
-
整数二分算法思路:寻找红色区间的右边界
-
获取区间中点下标
int mid = (l + r) >> 1; -
判断
mid下标对应元素是否满足目标区间性质bool flag = check_red(mid);-
当
flag为true时:证明此时mid对应元素满足红色区间,且需要寻找红色区间的右边界,那么更新区间- 此时
mid对应元素在红色区间内
//此时区间[l,r] ---> [mid,r] if(flag == true){ l = mid; } - 此时
-
当
flag为flase时:证明此时mid对应元素不满足红色区间,且需要寻找红色区间的右边界,那么更新区间- 此时
mid对应元素在绿色区间
//此时区间[l,r] ---> [l,mid - 1] if(flag == false){ r = mid - 1; } - 此时
-
-
查询算法模板,决定是否需要在
mid赋值时加一-
此时原区间
[ l , r ]被划分为[ l , mid - 1 ]与[ mid , r ],此时mid属于右区间,使用第二个模板;因此需要加一int mid = (l + r + 1) >> 1; -
为什么?
-
整数二分算法思路:寻找绿色区间的左边界
-
获取区间中点下标
int mid = (l + r) >> 1; -
判断
mid下标对应元素是否满足目标区间性质bool flag = check_green(mid);-
当
flag为true时:证明此时mid对应元素满足绿色区间,且需要寻找绿色区间的左边界,那么更新区间- 此时
mid对应元素在绿色区间内
//此时区间[l,r] ---> [l,mid] if(flag == true){ r = mid; } - 此时
-
当
flag为flase时:证明此时mid对应元素不满足绿色区间,且需要寻找绿色区间的右边界,那么更新区间- 此时
mid对应元素在红色区间
//此时区间[l,r] ---> [mid + 1,r] if(flag == false){ l = mid + 1; } - 此时
-
-
查询算法模板,决定是否需要在
mid赋值时加一-
此时原区间
[ l , r ]被划分为[ l , mid ]与[ mid + 1 , r ],此时mid属于左区间,使用第一个模板;因此不需要加一int mid = (l + r) >> 1; -
为什么?
-
整数二分之死循环:为什么更新后的区间下标会影响mid 的赋值
-
结论:
-
当原区间
[ l , r ]被划分为[ l , mid ]与[ mid + 1 , r ]int mid = (l + r) >> 1; -
原区间
[ l , r ]被划分为[ l , mid - 1 ]与[ mid , r ]int mid = (l + r + 1) >> 1;
-
-
为什么:
-
因为
c++中的整数除法是下取整,考虑特例。//当 r = l + 1 时, int mid = (l + r) >> 1 = l -
当原区间
[ l , r ]被划分为[ l , mid - 1 ]与[ mid , r ]时//此时,因为mid = l; //因此,区间更新[l,r] ---> [l,mid - 1]与[mid,r] // ---> [l,l - 1]与[l,r],陷入死循环
-
-
补上加一之后:
-
因为
c++中的整数除法是下取整,考虑特例。//当 r = l + 1 时, int mid = (l + r + 1) >> 1 = r; -
当原区间
[ l , r ]被划分为[ l , mid - 1 ]与[ mid , r ]时//此时,因为mid = l; //因此,区间更新[l,r] ---> [l,mid - 1]与[mid,r] // ---> [l,l]与[r,r],不满足while(l < r),跳出即可,不会陷入死循环
-
整数二分在处理实际问题时的思考方式
-
为原区间元素选择
check(),并获取更新后的区间下标 -
根据更新后的区间下标,选择对应的算法模板
-
当
mid位于更新后的左区间,则不需要加一//[l,r] 被划分为 [l,mid] 与 [mid + 1,r] -
当
mid位于更新后的右区间,则需要加一//[l,r] 被划分为 [l,mid - 1] 与 [mid,r]
-
算法模板一:
-
适用条件:区间
[ l , r ]被划分成[ l , mid ]与[mid + 1,r]时使用 -
模板代码:
int integer_binary_1(int l,int r) { while(l < r) { int mid = (l + r) >> 1; if(check(mid)) r = mid;//check(mid) 判断mid 是否满足某一性质 else l = mid + 1; } return l; }
算法模板二:
-
适用条件:区间
[ l , r ]被划分成[ l , mid - 1 ]与[ mid ,r]时使用 -
模板代码:
int integer_binary_1(int l,int r) { while(l < r) { int mid = (l + r + 1) >> 1;//区别一:选择mid 时是否需要 + 1 if(check(mid)) l = mid;//区别二:check(mid) 时的区间更新 else l = mid - 1; } return l; }
样例展示:
-
题目描述:
-
输入输出展示:
关键思路:查找number 的左边界为例
-
先确定
mid下标int mid = (l + r) >> 1; -
根据目标,选择性质,一定要将区间一分为二
if(nums[mid] <= number) -
确定区间的更新方式,注意,一定要联系二分的目标
- 目标:查找
number出现的左边界 - 建议画图理解:当
nums[mid] <= number成立时,说明,目标所在区间为[l,mid]之间,因此,我们要更新r;如果更新l,则错过了目标
if(nums[mid] <= number) { r = mid; }else{ l = mid + 1; } //[l,r] ---> [l,mid] 与 [mid + 1,r]; - 目标:查找
-
根据更新方式,选择对应模板
- 此时
mid被划分在了左区间,因此不需要在初始赋值时进行加一操作
- 此时
完整代码:c++
#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int nums[N];
int binary_start(int l,int r,int number)//寻找num 出现的左边界
{
while(l < r)
{
int mid = (l + r) >> 1;
if(nums[mid] >= number)
{
r = mid;
}else{
l = mid + 1;
}
}
return l;
}
int binary_end(int l,int r,int number)//寻找num 出现的右边界
{
while(l < r)
{
int mid = (l + r + 1) >> 1;
{
if(nums[mid] <= number)
{
l = mid;
}else{
r = mid - 1;
}
}
}
return l;
}
int main()
{
//处理IO
int n,q;
scanf("%d%d",&n,&q);
for(int i = 0;i < n;i++) scanf("%d",&nums[i]);
while(q--)
{
int number;
scanf("%d",&number);//读入每次询问
int start = binary_start(0,n - 1,number);//寻找number 出现的左边界
if(nums[start] != number)//当number 左边界不存在即待查询的数number 不在原数组中
{
cout<< "-1 -1" << endl;
continue;
}else{
cout << start << ' ';
int end = binary_end(0,n - 1,number);//寻找num 出现的右边界
cout << end << endl;//若左边界存在,那么有边界一定存在(当number 仅出现一次,左右边界相同 )
}
}
}
完整代码:Java
import java.io.BufferedInputStream;
import java.util.Scanner;
public class Main {
public int binary_start(int l, int r,int number,int[] nums){//寻找number 出现的左边界
while (l < r){
int mid = (l + r) >> 1;
if(nums[mid] >= number){
r = mid;
}else{
l = mid + 1;
}
}
return l;
}
public int binary_end(int l,int r,int number,int[] nums){//寻找number 出现的有边界
while (l < r){
int mid = (l + r + 1) >> 1;
if(nums[mid] <= number){
l = mid;
}else{
r = mid - 1;
}
}
return l;
}
public static void main(String[] args) {
Main ma = new Main();
//处理IO
Scanner sc = new Scanner(new BufferedInputStream(System.in));
int n,q;
n = sc.nextInt();
q = sc.nextInt();
int nums[] = new int[n];
for (int i = 0;i < n;i++){
nums[i] = sc.nextInt();
}
while (q-- > 0){//处理q 次询问
int number = sc.nextInt();
int start = ma.binary_start(0,n - 1,number,nums);
if(nums[start] != number){
System.out.println("-1 -1");
continue;
}else{
System.out.print(start + " ");
int end = ma.binary_end(0,n - 1,number,nums);
System.out.println(end);
}
}
}
}