二分法(红蓝染色)
二分法(红蓝染色)
思路来源于B站UP主五点七边。
二分法的介绍不用过多,虽然思想简单,但是细节却很多,循环条件中是否含有等于,过程中是left是mid+1还是left=mid,傻傻分不清楚。
初始方法
在二分法之前,都是从左到右或者从右到左逐个遍历,找到最后结果返回,例如,如果想要找到第一个大于等于3的元素位置,可以从前往后或者从后往前遍历
红指针从左开始遍历,直到碰到等于3的情况
或者蓝指针从右开始遍历,直到碰到不等于3的情况
对于几乎所有有序数组,都可以划分成两块元素(红色 or 蓝色),针对条件取得红色、蓝色边界即可,
例如要得到
第一个大于等于3 的元素,为上图蓝色边界第一个元素
最后一个小于3 的元素,为上图红色边界的最后一个元素
第一个大于3 的元素,为下图蓝色边界的第一个元素
最后一个小于等于3 的元素,为下图红色边界的最后一个元素
二分
其实对于上面双指针移动,中间会有很多不必要的操作,由于数据已经排序,所以例如红指针如果一开始就在2数字的位置,能立即判断2左侧均为红色,中间就略过了一部分判断操作,二分由此产生。
具体步骤
- 首先红指针在0的左侧,蓝指针在数组的最右侧(均在数组外面,防止一开始就给数组上色导致错误)。
- 然后取中间位置,判断该位置是 红色 还是 蓝色,然后将对应的指针指向中间位置(将该中间及对应左(右)侧“染色”)。
- 循环步骤二,直到 红指针和蓝指针 相差 1 (即左右指针相邻)
例如上方第一个栗子
通用模板
Java版本
int red = -1;
int blue = len;
while (red + 1 != blue){
//尽量少使用 (red+blue)/2,虽然两者等同,但用加法容易溢出,使用位运算加快运算
//int mid = red + (blue-red)/2;
int mid = red + ((blue - red) >> 1);
if(符合左侧条件){
red = mid;
}else{
blue = mid;
}
}
上面第一个栗子的代码
public static void main(String[] args) {
int[] arr = {1,1,2,2,3,3,4,4,5,5};
int left = -1;
int right = arr.length;
while (left + 1 != right){
//int mid = left + (right-left)/2;
int mid = left + ((right - left) >> 1);
if(arr[mid] >= 3){
right = mid;
}else{
left = mid;
}
}
System.out.println(right);
}
注意点
- 大于等于、小于,这其实是一类情况,只是取得一个是红色指针,一个蓝色指针而已。小于等于、大于,同上。
- 最初的红蓝指针指向的是数组外侧,防止一开始就给数组染色。
- 只有红色指针和蓝色指针相差一(红蓝指针相邻)的时候退出。