参考自B站灵茶山艾府

红蓝染色法

我们用left代表蓝色, right代表红色, 蓝色和红色一定是互斥的, 比如蓝色规定代表<= target的区域, 那么红色就代表>target的区域

我们如何才能表示成红蓝染色呢?我们可以采用"(" / "[" left, right "]" / ")"的形式。我们规定"["表示元素本身以及区域以内的元素是未染色的,规定"("则不包含元素本身。括号内的是还未染色的区域,括号外的是已经染色的区域,这样我们就可以形象地表示出红色区域,蓝色区域以及未处理的元素区域。

我们通过不断将数组的中间元素和要寻找的元素相比较,来染色未处理得区域,直到所有区域都染色完毕。

值得一提的是,left和right的移动应该根据它们代表的是开区间还是闭区间来移动。如果是闭区间的话,left或right不能指向mid本身,因为闭区间表示元素本身以及区域内的元素是未处理的,但是很明显,mid已经处理了。

由于数组每次都收缩一半,最终的时间复杂度是O(logn)的。

模板

这里就根据灵神推荐的开区间写法来写。

查找指定元素

class Solution {
    public int search(int[] nums, int target) {
        int ans = lowerBound(nums, target);
        return ans < nums.length && nums[ans] == target ? ans : -1;
    }

    private int lowerBound(int[] nums, int target) {
        int left = -1;
        int right = nums.length;
        while (left + 1 < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid;
            } else {
                right = mid;
            }
        }
        return right;
    }
}

查找最后一个</<= 或者 第一个>/>=

class Solution {
    public int search(int[] nums, int target) {
        int ans = lowerBound(nums, target);
        return ans < nums.length && nums[ans] == target ? ans : -1;
    }

    private int lowerBound(int[] nums, int target) {
        int left = -1;
        int right = nums.length;
        while (left + 1 < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid;
            } else {
                right = mid;
            }
        }
        return left/right;
    }
}

边界条件是

left == -1 或者 right == nums.length

总结

lowerBound:寻找第一个大于等于target的下标

upperBound:寻找第一个大于target的下标

如果要找最后一个小于target的下标,就是lowerBound(target) - 1

如果要找最后一个小于等于target的下标,就是upperBound(target) - 1或者lowerBound(target + 1) - 1

PS

如果只有一个二分值,有些时候要进行边界条件判断,因为可能会引起数组下标越界。

但是如果是两个二分值之差确定个数或者距离,我们没有这种烦恼,相对位置是确定的,不用进行边界条件判断。