前言

之前对二分理解不够透彻,现在学习后又有点心得了。

二分法之我个人最容易理解的写法

int binarySearch(int[] nums, int target) {
    int left = 0;
    int right = nums.length - 1; // 注意
    while(left <= right) {
        int mid = left + (right - left) / 2;
        if(nums[mid] == target)
            return mid;
        else if (nums[mid] < target)
            left = mid + 1; // 注意
        else if (nums[mid] > target)
            right = mid - 1; // 注意
    }
    return -1;
}

这个写法重点在于,是闭区间,因为对mid位置,如果不满足,缩减就是mid-1和mid+1。但是这样会有一种情况发生,那就是对于[1,2,2,2,3],我们无法利用找到左侧边界的2以及右侧边界的2.因此这样就有了其他的二分写法,左侧边界的搜索,右侧边界的搜索

左侧边界搜索的二分

int left_bound(int[] nums, int target) {
    if (nums.length == 0) return -1;
    int left = 0;
    int right = nums.length; // 注意
    while (left < right) { // 注意
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            right = mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid; // 注意
        }
    }
    return left;
//也可以判断,如果left越界了,就应该返回-1,表示没有找到
}

左侧边界的写法,首先变化的就是由原来的闭区间变成了开区间,整个区间是[0,nums.length)。因此,while中也变成了小于,而不再是等于。
本例中,如果在相等时,直接返回就和上面的普通写法一样,但是注意,左侧边界的代码中,写法时right=mid,相等时,不断缩小右边的范围,这样就能形成左侧边界的搜索。

左侧边界的不搜索的二分

正如左侧搜索中提到的,不搜索,直接返回自然也是可以的,

int left_bound(int[] nums, int target) {
    if (nums.length == 0) return -1;
    int left = 0;
    int right = nums.length; // 注意
    while (left < right) { // 注意
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            return mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid; // 注意
        }
    }
    return left;

右侧边界搜索的二分

所有右侧边界的搜索也很容易理解了,相等时,缩小左侧区域,由于左边是闭区间,所以,相等时,是左侧+1,

int right_bound(int[] nums, int target) {
    if (nums.length == 0) return -1;
    int left = 0, right = nums.length;
    while (left < right) {
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            left = mid + 1; // 注意
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid;
        }
    }
    return left - 1; // 注意
}

右侧边界不搜索

直接返回

int right_bound(int[] nums, int target) {
    if (nums.length == 0) return -1;
    int left = 0, right = nums.length;
    while (left < right) {
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            return mid; 
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid;
        }
    }
    return left - 1; // 注意
}

补充

如果非要用闭区间进行搜索,以右侧为例

int right_bound(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid - 1;
        } else if (nums[mid] == target) {
            left = mid + 1;
        }
    }
    // 需要检查越界
    if (right < 0 || nums[right] != target)
        return -1;
    return right;
}

左侧的也不难

int right_bound(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid - 1;
        } else if (nums[mid] == target) {
            right = mid - 1;
        }
    }
    // 需要检查越界
// 最后要检查 left 越界的情况
    if (left >= nums.length || nums[left] != target)
        return -1;
    return left;
}