【算法】二分查找

2022年5月15日

目录

使用场景

二分查找框架

寻找一个数(基本二分搜索)

寻找左侧边界

寻找右侧边界

逻辑梳理


使用场景

寻找一个数

寻找左侧边界

寻找右侧边界

二分查找框架

代码

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

技巧:将所有情况用else if写清楚,展现所有细节。

寻找一个数(基本二分搜索)

题目:搜索一个数,如果存在,返回其索引,否则返回-1。

代码框架:

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

问题:

  1. 为什么是while(left <= right)?

    因为在本段代码中,区间是两端都闭区间[left,right](right = nums.length - 1;),而不是左闭右开区间[left,right)

  2. 为什么判断后是left=mid+1,right=mid-1

    因为本算法的搜索区间是闭区间,即[left,right],当发现索引mid不是要找的target时,要去搜[left,mid-1]或[mid+1,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 - left) / 2;
        if (nums[mid] == target) {
            right = mid; //收紧右边界,向左收缩
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid; // 注意
        }
    }
    return left;
}

问题:

  1. 为什么是while(left < right)?

    答:因为代码中,right = nums.length;,所以每次搜索区间都是[left,right)

  2. 为什么left=mid+1,right=mid

    答:因为搜索区间是[left,right)左闭右开,所以当nums[mid]被检测之后,下一步的搜索区间应该去掉mid,然后分割成两个区间,即[left,mid)和[mid+1,right)

  3. 是不是可以使用两边都闭的搜索区间?

    代码如下:

    int left_bound(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        // 搜索区间为 [left, right]
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
            // 搜索区间变为 [mid+1, right]
                left = mid + 1;
            } else if (nums[mid] > target) {
            // 搜索区间变为 [left, mid-1]
                right = mid - 1;
            } else if (nums[mid] == target) {
            // 收缩右侧边界
                right = mid - 1;
            }
        }
        // 检查出界情况
        if (left >= nums.length || nums[left] != target)
            return -1;
        return left;
    }

寻找右侧边界

代码模板:

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 - left) / 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; // 注意
}

问题:

  1. 为什么最后返回left - 1 而不是返回left?为什么不返回right?

    答:while循环的终止条件是left==right,所以left和right一样,也可以返回right-1。

  2. 是不是可以使用两边都闭的搜索区间?

    代码如下:

    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;
            }
        }
        // 这⾥改为检查 right 越界的情况,⻅下图
        if (right < 0 || nums[right] != target)
            return -1;
        return right;
    }

逻辑梳理

  1. 当我们初始化right = nums.length - 1时,就决定了搜索区间是[left, right],也决定了循环while (left <= right)left = mid+1、right = mid-1

  2. 当我们初始化right = nums.length时,就决定了搜索区间是[left, right),也决定了循环while (left < right)left = mid+1、right = mid

  3. 找target的最左侧索引时,nums[mid] == target时要收紧右侧边界。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值