二分法(红蓝染色)

二分法(红蓝染色)

思路来源于B站UP主五点七边

二分法的介绍不用过多,虽然思想简单,但是细节却很多,循环条件中是否含有等于,过程中是left是mid+1还是left=mid,傻傻分不清楚。

初始方法

在二分法之前,都是从左到右或者从右到左逐个遍历,找到最后结果返回,例如,如果想要找到第一个大于等于3的元素位置,可以从前往后或者从后往前遍历

1

红指针从左开始遍历,直到碰到等于3的情况

2

或者蓝指针从右开始遍历,直到碰到不等于3的情况

3

对于几乎所有有序数组,都可以划分成两块元素(红色 or 蓝色),针对条件取得红色、蓝色边界即可,

例如要得到

第一个大于等于3 的元素,为上图蓝色边界第一个元素
最后一个小于3 的元素,为上图红色边界的最后一个元素
第一个大于3 的元素,为下图蓝色边界的第一个元素
最后一个小于等于3 的元素,为下图红色边界的最后一个元素

4

二分

其实对于上面双指针移动,中间会有很多不必要的操作,由于数据已经排序,所以例如红指针如果一开始就在2数字的位置,能立即判断2左侧均为红色,中间就略过了一部分判断操作,二分由此产生。

具体步骤

  • 首先红指针在0的左侧,蓝指针在数组的最右侧(均在数组外面,防止一开始就给数组上色导致错误)。
  • 然后取中间位置,判断该位置是 红色 还是 蓝色,然后将对应的指针指向中间位置(将该中间及对应左(右)侧“染色”)。
  • 循环步骤二,直到 红指针和蓝指针 相差 1 (即左右指针相邻)

例如上方第一个栗子

5

6

7

8

9

10

11

12

通用模板

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);
}

注意点

  • 大于等于、小于,这其实是一类情况,只是取得一个是红色指针,一个蓝色指针而已。小于等于、大于,同上。
  • 最初的红蓝指针指向的是数组外侧,防止一开始就给数组染色
  • 只有红色指针和蓝色指针相差一(红蓝指针相邻)的时候退出。
posted @ 2022-10-11 16:57  抱糖果彡  阅读(535)  评论(0编辑  收藏  举报