二分查找细节问题

二分查找细节问题


1. 基本的二分查找

1.1 两种实现

// 情况一:right = nums.length
int binary_search(int[] nums, int target) {
    // 不需要在这里判断,长度为 0,不能进入循环,且不能通过最后的判断
    // if (nums.length == 0) return -1;
    
    // 查找区间为 [left, right)
    int left = 0, right = nums.length;
    while (left < right) {
        // 避免了 left + right 可能溢出的情况
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            return mid;
        } else if (nums[mid] < target) {
            // 因为查找区间左侧是闭区间,left 指向的元素能被搜索,所以赋值为 mid + 1
            left = mid + 1;
        } else if (nums[mid] > target) {
            // 因为查找区间右侧是开区间,right 指向的元素不能被搜索,所以不能赋值为 mid - 1
            right = mid;
        }
    }
    // 因为循环的结束条件是 left = right,它们指向的元素未被判断,所以还要再判断这个遗漏的元素
    return nums[left] == target ? left : -1;
}
// 情况二:right = nums.length - 1
int binary_search(int[] nums, int target) {
    // 查找区间为 [left, right]
    int left = 0, right = nums.length - 1; 
    while (left <= right) {
        // 避免了 left + right 可能溢出的情况
        int mid = left + (right - left) / 2;
        if (nums[mid] == targer) {
            return mid;
        } else if (nums[mid] > target) {
            // 因为查找区间右侧是闭区间,right 指向的元素能被搜索,所以赋值为 mid - 1
            right = mid - 1;
        } else if (nums[mid] < target) {
            // 因为查找区间左侧是闭区间,left 指向的元素能被搜索,所以赋值为 mid + 1
            left = mid + 1;
        }
    }
    // 不存在遗漏的情况
    return -1;
}

2. 寻找左侧边界的二分查找

遗漏: 指第一次 mid 定位的那个元素刚好就是 target 元素所求边界的特殊情况

2.1 两种实现

// 情况一:right = nums.length
public int searchLeft(int[] nums, int target) {
    // 查找区间为 [left, right)
    int left = 0, right = nums.length;
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            // 因为要查找左边的临界值,所以要将查找区间的右侧缩小,右侧是开区间,right 指向的值不能被判断,产生遗漏
            right = mid;
        } else if (nums[mid] < target) {
            // 因为查找区间左侧是闭区间,left 指向的元素能被搜索,所以赋值为 mid + 1
            left = mid + 1;
        } else {
            // 因为查找区间右侧是开区间,right 指向的元素不能被搜索,所以不能赋值为 mid - 1
            right = mid;
        }
    }
    // 1. 越界。target 比所有的值都要大
    // 2. 没有越界。因为循环的结束条件是 left = right,这个遗漏的元素就是 left 指向的元素
    if (left == nums.length || nums[left] != target) {
        return -1;
    }
    return left;
}
// 情况二:right = nums.length - 1
public int searchLeft(int[] nums, int target) {
    // 查找区间为 [left, right]
    int left=0, right=nums.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            // 因为查找区间右侧是闭区间,right 指向的元素能被搜索,所以赋值为 mid - 1。遗漏的元素为 right + 1
            right = mid - 1;
        } else if (nums[mid] < target) {
            // 因为查找区间左侧是闭区间,left 指向的元素能被搜索,所以赋值为 mid + 1
            left = mid + 1;
        } else {
            // 因为查找区间右侧是闭区间,right 指向的元素能被搜索,所以赋值为 mid - 1
            right = mid - 1;
        }
    }
    // 1. 越界。target 比所有的值都要大
    // 2. 没有越界。因为循环的结束条件是 left = right + 1,这个遗漏的元素就是 left 指向的元素
    if (left == nums.length || nums[left] != target) {
        return -1;
    }
    return left;
}

3. 寻找右侧边界的二分查找

3.1 两种实现

// 情况一:right = nums.length
public int searchRight(int[] nums, int target) {
    // 查找区间为 [left, right)
    int left=0, right=nums.length;
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            // 因为要查找右边的临界值,所以要将查找区间的左侧缩小,左侧是闭区间,
            // left 指向的值能被判断,所以指向 mid + 1,同时产生遗漏
            left= mid + 1;
        } else if (nums[mid] < target) {
            // 因为查找区间左侧是闭区间,left 指向的元素能被搜索,所以赋值为 mid + 1
            left = mid + 1;
        } else {
            // 因为查找区间右侧是开区间,right 指向的元素不能被搜索,所以不能赋值为 mid - 1
            right = mid;
        }
    }
    // 处理越界情况和遗漏判断
    if (right == 0 || nums[right - 1] != target) {
        return -1;
    }
    return right - 1;
}
// 情况二:right = nums.length - 1
public int searchRight(int[] nums, int target) {
	// 查找区间为 [left, right]
    int left=0, right=nums.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            // 因为要查找右边的临界值,所以要将查找区间的左侧缩小,左侧是闭区间,
            // left 指向的值能被判断,所以指向 mid + 1,同时产生遗漏
            left= mid + 1;
        } else if (nums[mid] < target) {
            // 因为查找区间左侧是闭区间,left 指向的元素能被搜索,所以赋值为 mid + 1
            left = mid + 1;
        } else {
            // 因为查找区间右侧是闭区间,right 指向的元素能被搜索,所以赋值为 mid - 1
            right = mid - 1;
        }
    }
	// 处理越界情况和遗漏情况
    if (right < 0 || nums[right] != target) {
        return -1;
    }
    return right;
}

参考:二分查找详解

posted @ 2021-03-17 20:04  心平气和Coding  阅读(126)  评论(0)    收藏  举报