手撕二分法查找(边界值注意点)

前文介绍

  二分法查找相信很多同学都是一看就会,一做就废,对于一些边界的条件和left,right的取值还不是很明确,那你看这个文章就能很好解决你的疑问了,对于完全0基础的同学,可以结合这个视频,来看我的文章会比较好,谢谢哈。

关于闭区间和开区间

  所谓闭区间就是区间的包括边界值,如【1,2】的正整数区间,包含1,2这两个边界值,【1,1】区间包括1这个边界值,而开区间就不会包括边界值,如(1,2),(1,1)的正整数区间,没有包含任何数,也是无效区间,为空,(1,3)这样的区间才包含了2这个数值。

二分法实战(LeetCode704题)

原题链接

  理解了闭区间和开区间的概念,我们通过一道金典二分法查找算法题来实战

题目要求查找出target并返回下标,没有找到就返回返回-1,我们就分别使用左闭合右闭合区间,左闭合右开区间的二分法查找算法来解决

左闭合右闭合区间写法

JavaScript

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
// 左右闭合区间写法
var search = function(nums, target) {
// 分别定义左右起始下标,因为是左右闭合包含边界值,
// 所以分别定义数组第一个下标和最后一个下标
    let left = 0;
    let right = nums.length-1;
// while的边界值条件判断,因为要满足while的条件才能进入循环,
// 所以区间要为有效区间,要有值,如【1,2】有1,2,【1,1】有1,
// 而【2,1】就没有值,为无效区间,所以right要大于等于left才有效
    while(left<=right){
// 取mid中间值判断
        let mid = Math.floor((left+right) / 2) 
//当mid下标对应的值大于target,说明包括mid下标和mid下标右边的值都是大于target的,
// 不是我们要的区间范围,要进行缩小(缩小的区间不包括mid),
//所以修改right(右闭合区间包括right)的值为mid-1,
// 如果right为mid,而mid下标的值是大于target的,不是我们要的范围,就不准确了
        if(nums[mid] > target){
            right = mid - 1;
//同理,当mid下标对应的值小于target,说明包括mid下标和mid下标左边的值都是小于target的,
//不是我们要的区间范围,要进行缩小(缩小的区间不包括mid),
//所以修改left(左闭合区间包括left)的值为mid+1,
//如果right为mid,而mid下标的值是小于target的,不是我们要的范围,就不准确了
        }else if(nums[mid] < target){
            left = mid + 1;
        }else{
            return mid
        }
    }
    return -1;
    
};

C++

#include <vector>
#include <cmath>  // 用于 std::floor

int search(const std::vector<int>& nums, int target) {
    // 分别定义左右起始下标,因为是左右闭合包含边界值,
    // 所以分别定义数组第一个下标和最后一个下标
    int left = 0;
    int right = nums.size() - 1;
    
    // while的边界值条件判断,因为要满足while的条件才能进入循环,
    // 所以区间要为有效区间,要有值,如【1,2】有1,2,【1,1】有1,
    // 而【2,1】就没有值,为无效区间,所以right要大于等于left才有效
    while (left <= right) {
        // 取mid中间值判断
        int mid = std::floor((left + right) / 2); 

        // 当mid下标对应的值大于target,说明包括mid下标和mid下标右边的值都是大于target的,
        // 不是我们要的区间范围,要进行缩小(缩小的区间不包括mid),
        // 所以修改right(右闭合区间包括right)的值为mid-1,
        // 如果right为mid,而mid下标的值是大于target的,不是我们要的范围,就不准确了
        if (nums[mid] > target) {
            right = mid - 1;

     // 同理,当mid下标对应的值小于target,说明包括mid下标和mid下标左边的值都是小于target的,
        // 不是我们要的区间范围,要进行缩小(缩小的区间不包括mid),
        // 所以修改left(左闭合区间包括left)的值为mid+1,
        // 如果right为mid,而mid下标的值是小于target的,不是我们要的范围,就不准确了
        } else if (nums[mid] < target) {
            left = mid + 1;

        // 如果找到了target,则返回mid
        } else {
            return mid;
        }
    }
    
    // 如果未找到target,则返回-1
    return -1;
}

Java

public class BinarySearch {

    /**
     * 在有序数组中查找目标值的索引
     *
     * @param nums  有序数组
     * @param target 目标值
     * @return 目标值的索引,如果未找到则返回 -1
     */
    public static int search(int[] nums, int target) {
        // 分别定义左右起始下标,因为是左右闭合包含边界值,
        // 所以分别定义数组第一个下标和最后一个下标
        int left = 0;
        int right = nums.length - 1;

        // while的边界值条件判断,因为要满足while的条件才能进入循环,
       // 所以区间要为有效区间,要有值,如【1,2】有1,2,【1,1】有1,
       // 而【2,1】就没有值,为无效区间,所以right要大于等于left才有效
        while (left <= right) {
            // 取mid中间值判断
            int mid = left + (right - left) / 2; // 避免(left + right)直接相加可能导致的整数溢出

        // 当mid下标对应的值大于target,说明包括mid下标和mid下标右边的值都是大于target的,
        // 不是我们要的区间范围,要进行缩小(缩小的区间不包括mid),
        // 所以修改right(右闭合区间包括right)的值为mid-1,
        // 如果right为mid,而mid下标的值是大于target的,不是我们要的范围,就不准确了
            if (nums[mid] > target) {
                right = mid - 1;

     // 同理,当mid下标对应的值小于target,说明包括mid下标和mid下标左边的值都是小于target的,
        // 不是我们要的区间范围,要进行缩小(缩小的区间不包括mid),
        // 所以修改left(左闭合区间包括left)的值为mid+1,
        // 如果right为mid,而mid下标的值是小于target的,不是我们要的范围,就不准确了
            } else if (nums[mid] < target) {
                left = mid + 1;

                // 如果找到了target,则返回mid
            } else {
                return mid;
            }
        }

        // 如果未找到target,则返回-1
        return -1;
    }
}

左闭合右开区间写法

JavaScript

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
// 左闭合右开区间写法
var search = function(nums, target) {
// 分别定义左右起始下标,因为是左闭合右开区,
// 所以左边包含边界值,右边不包含边界值
// 所以分别定义数组第一个下标和数组的长度
    let left = 0;
    let right = nums.length;
// while的边界值条件判断,因为要满足while的条件才能进入循环,
// 所以区间要为有效区间,要有值,如【1,2)有1,【1,3)有1,2
// 而【1,1),【2,1)就没有值,为无效区间,所以right要大于left(不能等于)才有效
    while(left<right){
// 取mid中间值判断
        let mid = Math.floor((left+right) / 2) 
//当mid下标对应的值大于target,说明包括mid下标和mid下标右边的值都是大于target的,
// 不是我们要的区间范围,要进行缩小(缩小的区间不包括mid),
//所以修改right(右开区间是不包括right)的值mid,
//如果right为mid-1,而mid-1下标的值是我们进行缩小的区间范围,但不包括(右开区间),所以是不对的
        if(nums[mid] > target){
            right = mid;
//同理,当mid下标对应的值小于target,说明包括mid下标和mid下标左边的值都是小于target的,
//不是我们要的区间范围,要进行缩小(缩小的区间不包括mid),
//所以修改left(左闭合区间包括left)的值为mid+1,
//如果left为mid,而mid下标的值是小于target的,不是我们要的范围,就不准确了
        }else if(nums[mid] < target){
            left = mid + 1;
        }else{
            return mid
        }
    }
    return -1;
    
};

C++

#include <vector>
#include <iostream>

/**
 * 在有序数组中查找目标值的索引(左闭合右开区间写法)
 *
 * @param nums  有序数组
 * @param target 目标值
 * @return 目标值的索引,如果未找到则返回 -1
 */
int search(const std::vector<int>& nums, int target) {
// 分别定义左右起始下标,因为是左闭合右开区间,
// 所以左边包含边界值,右边不包含边界值
// 所以分别定义数组第一个下标和数组的长度
    int left = 0;
    int right = nums.size(); // 注意这里是 size(),返回的是无符号整数,但在这里作为右开区间边界没问题

// while的边界值条件判断,因为要满足while的条件才能进入循环,
// 所以区间要为有效区间,要有值,如【1,2)有1,【1,3)有1,2
// 而【1,1),【2,1)就没有值,为无效区间,所以right要大于left(不能等于)才有效
    while (left < right) {
// 取 mid 中间值判断,注意防止 (left + right) 直接相加可能导致的整数溢出
// 但由于 right 是 nums.size(),它实际上比数组的最大索引大 1(右开区间),
// 所以这里直接使用 (left + right) / 2 也是安全的,因为 right 不会等于数组中的任何有效索引
        int mid = left + (right - left) / 2;

 //当mid下标对应的值大于target,说明包括mid下标和mid下标右边的值都是大于target的,
 //不是我们要的区间范围,要进行缩小(缩小的区间不包括mid),
 //所以修改right(右开区间是不包括right)的值mid,
// 如果right为mid-1,而mid-1下标的值是我们进行缩小的区间范围,但不包括(右开区间),所以是不对的
        if (nums[mid] > target) {
            right = mid;

//同理,当mid下标对应的值小于target,说明包括mid下标和mid下标左边的值都是小于target的,
//不是我们要的区间范围,要进行缩小(缩小的区间不包括mid),
//所以修改left(左闭合区间包括left)的值为mid+1,
//如果letf为mid,而mid下标的值是小于target的,不是我们要的范围,就不准确了
        } else if (nums[mid] < target) {
            left = mid + 1;

            // 如果找到了 target,则返回 mid
        } else {
            return mid;
        }
    }

    // 如果未找到 target,则返回 -1
    return -1;
}

Java 

/**
 * 在有序数组中查找目标值的索引(左闭合右开区间写法)
 *
 * @param nums  有序数组
 * @param target 目标值
 * @return 目标值的索引,如果未找到则返回 -1
 */
public int search(int[] nums, int target) {
// 分别定义左右起始下标,因为是左闭合右开区,
// 所以左边包含边界值,右边不包含边界值
// 所以分别定义数组第一个下标和数组的长度
    int left = 0;
    int right = nums.length; // 注意这里是长度,它实际上比数组的最大索引大1(右开区间)

// while的边界值条件判断,因为要满足while的条件才能进入循环,
// 所以区间要为有效区间,要有值,如【1,2)有1,【1,3)有1,2
// 而【1,1),【2,1)就没有值,为无效区间,所以right要大于left(不能等于)才有效
    while (left < right) {
// 取 mid 中间值判断,注意防止 (left + right) 直接相加可能导致的整数溢出                   // 但在这个特定情况下,由于 right 是 nums.length,它不会等于数组中的任何有效索引,
// 所以直接使用 (left + right) / 2 也是安全的
        int mid = left + (right - left) / 2;

//当mid下标对应的值大于target,说明包括mid下标和mid下标右边的值都是大于target的,
// 不是我们要的区间范围,要进行缩小(缩小的区间不包括mid),
//所以修改right(右开区间是不包括right)的值mid,
//如果right为mid-1,而mid-1下标的值是我们进行缩小的区间范围,但不包括(右开区间),所以是不对的
        if (nums[mid] > target) {
            right = mid;

//同理,当mid下标对应的值小于target,说明包括mid下标和mid下标左边的值都是小于target的,
//不是我们要的区间范围,要进行缩小(缩小的区间不包括mid),
//所以修改left(左闭合区间包括left)的值为mid+1,
//如果left为mid,而mid下标的值是小于target的,不是我们要的范围,就不准确了
        } else if (nums[mid] < target) {
            left = mid + 1;

            // 如果找到了 target,则返回 mid
        } else {
            return mid;
        }
    }

    // 如果未找到 target,则返回 -1
    return -1;
}

以上就是二分法查找中容易有问题的边界值取值问题,包括left,right的取值,while的循环条件,right和left与mid的关系,同学们也可以跳转链接看视频学习,谢谢大家

### Python 实现二分法查找时边界条件处理 在 Python 中实现二分法查找时,边界条件的处理至关重要。不同的边界定义方式会影响算法逻辑和终止条件的选择。 #### 左右闭区间 `[left, right]` 当采用左右闭区间的定义时,初始 `left` 和 `right` 分别指向数组的第一个元素和最后一个元素。循环继续执行直到 `left` 超过 `right`。每次迭代更新 `mid` 后,如果目标值位于左侧,则应将 `right` 更新为 `mid - 1`; 如果目标值位于右侧,则需把 `left` 设定为 `mid + 1`[^4]。 ```python def binary_search_closed_interval(nums, target): left = 0 right = len(nums) - 1 while left <= right: mid = (left + right) // 2 if nums[mid] == target: return mid elif nums[mid] < target: left = mid + 1 else: right = mid - 1 return -1 ``` #### 左闭区间 `(left, right]` 另一种常见的做法是以左右闭的形式来表示搜索范围,在这种情况下,`right` 初始化为数组长度而非最后一项索引。这意味着有效搜索空间不包含最右边的那个位置。因此,调整指针时只需改变左边界的值即可保持不变量成立。 ```python def binary_search_left_open_right_closed(nums, target): left = 0 right = len(nums) while left < right: mid = (left + right) // 2 if nums[mid] == target: return mid elif nums[mid] < target: left = mid + 1 else: right = mid return -1 ``` 这两种方法都可以有效地完成二分查找任务,但在实际应用中需要注意数据结构的特以及具体需求选择合适的版本。对于某些特定场景下的优化(如寻找第一个等于给定值的位置),可能还需要进一步修改上述模板中的细节部分[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值