二分查找及各类变种题模板

71 阅读2分钟

二分查找的概念本身很容易理解,这里不做赘述。其使用前提只有一个:单调数组

朴素二分查找

当在一个有向数组中,查找数组中是否有某个值,代码很简单:

const binarySearch = (nums, target) => {
    let l = 0;
    let r = nums.length - 1;
    while(l<=r) {
        const mid = (l+r) >> 1
        if(nums[mid]<target) {
            l = mid + 1
        } else if(nums[mid]>target) {
            r = mid - 1
        } else {
            return mid
        }
    }
    return -1
};

各类变种题及解题模板

实际使用时,用到朴素二分查找的情况反而更少,例如将4插入数组[1,2,2,3,3,4,4,5]中,插入后的数组保持单调递增,我们需要先找到小于4的边界(3的索引),再将4插入到这个边界后面。

如何理解这个查找过程

分区.png

首先将数组看作如图所示红蓝两个区域,红区代表所有小于target的值,蓝区代表所有不小于target的值。那么应该如何找到红区和蓝区的边界?

解题模板

模板如下:

// 通用模板
const binarySearch = (arr, isLeftPart) => {
    // 边界处理
    if(!isLeftPart(arr[0])) return [-1, 0];
    if(isLeftPart(arr[arr.length - 1])) return [arr.length-1, arr.length];
    let l = 0;
    let r = arr.length - 1;
    // 注意,条件是l<r-1
    while(l<r-1) {
        const mid = (l+r)>>1;
        // 说明mid处于红区,需要给l赋值mid
        if(isLeftPart(arr[mid])) {
            l = mid
        } else {
            r = mid
        }
    }
    return [l, r];
}
// 测试代码
const arr = [1,2,2,3,3,4,4,5];
const [_, idx] = binarySearch(arr, (val)=>val<4);
// insert
arr.splice(idx, 0, 4);
console.log(arr)

通用模板返回结构[l, r]l代表红区的右边界,r代表蓝区的左边界。

无论如何变化,这类变种题最后要找的一般就是l或者r

while的边界问题

朴素二分查找中,while条件是while(l<=r),而解题模板中则是while(l<r-1),这种边界是如何区分的?

我们需要明白,while代表终止条件,第一个不满足条件的情况即为终止时的状态。

以通用模板为例,终止时lr的指向应如图所示:

箭头指向.png

可以看到,这个终止态时,l === r-1,那么非终止态,也就是while循环条件应当是while(l<r-1).

同理,对于朴素二分查找,l===r时,仍要判断该值是否是查找值,只有当l>r才能断定查找完毕,因此while条件是while(l<=r)