【二分法】多种情况下的边界条件,区间选择汇总,小结

本文详细介绍了二分查找算法,包括左闭右闭和左闭右开两种区间形式,以及寻找特定值、最左满足条件边界、最右满足条件边界、最左插入位置和最右插入位置的应用。讨论了精度问题、解空间和序列有序的概念,并提供了多种情况的代码实现。

1 二分法

参考:
《算法通关之路》
labuladong的算法小抄

1.1 二分查找

  • 二分查找又称折半搜索算法,狭义来讲,二分查找是一种在有序数组查找某一特定元素的搜索算法;广义来讲,将问题的规模缩小到原有的一半;

  • 二分查找的细节问题很多注意!

  • 术语

    • target:要查找的值;

    • index:当前位置;

    • l和r:左右指针;

    • mid:左右指针的中点,用来确定我们应该向左查找还是向右查找的索引;

  • 解空间

  • 解空间:指题目所有可能的解构成的集合;可大不可小

  • 目标:在某个具体情况下判断其具体是哪一个,如果线性枚举所有的可能,枚举部分时间复杂度就为O(n);

  • 实数二分

    • 少部分题目的解空间包括小数,会涉及精度问题,例如求一个数x的平方根,答案误差在10-6次方都认为正确,解空间大小定义为[1,x],包括所有区间的实数,不仅仅整数而已,称为实数二分;这个时候有两种变化:

      • 1)更新答案的步长:例如之前的更新是 l = mid + 1,现在可能就不行了,因此这样可能会错过正确解,比如正确解恰好就在区间 [mid,mid+1] 内的某一个小数。
      • 2)判断条件时候考虑误差:由于精度问题,判断结束条件变成与答案的误差在某一个范围内。
  • 序列有序

  • 序列有序中的序列:不一定是数组、链表、有可能是其他数据结构;

  • 有些题目没有有序关键字,比如题目给了数组nums,并且没有限定nums有序,但限定了nums为非负,如果给nums进行前缀和或前缀或(位运算或),可以得到一个有序序列。

  • 一个中心

    • 折半才是二分法的灵魂

1.2 查找一个数

  • 1、区间选取问题

    • 由于定义的搜索区间为 [left, right],因此当 left <= right 的时候,搜索区间都不为空,此时我们都需要继续搜索。 也就是说终止搜索条件应该为 left <= right。

      举个例子容易明白一点。 比如对于区间 [4,4],其包含了一个元素 4,因此搜索区间不为空,需要继续搜索(试想 4 恰好是我们要找的 target,如果不继续搜索, 会错过正确答案)。而当搜索区间为 [left, right) 的时候,同样对于 [4,4),这个时候搜索区间却是空的,因为这样的一个区间不存在任何数字·。

1.2.1 左闭右闭

  • 代码实现
int binarySearch(vector<int>& nums,int target){
    //采用左闭右闭区间
    int left = 0;
    int right = nums.size()-1;
    while(left <= right){
        int mid = left + (right - left) / 2;
        if(nums[mid] > tagert)right = mid - 1;
        else if(nums[mid] < target)left = mid + 1;
        else return mid;
    }
    //左闭右闭区间结束,left=right+1 //此时,没有target,返回-1
    return -1;
}

1.2.2 左闭右开

  • 代码实现
int binarySearch(vector<int>& nums,int target){
    //采用左闭右开区间
    int left = 0;
    int right = num.size();
    while(left < right){//此处不同
        int mid = left + (right - left) / 2;
        if(nums[mid] < target)left = mid + 1;
        else if(nums[mid > target])rigth = mid;//此处不同
        else return mid;
    }
    return -1;
}

1.3 寻找最左满足条件的边界

  • 说明
    • 应该插入的位置,使得插入之后列表仍然有序;
    • 另外如果有多个满足条件的值,我们返回最左侧的。 比如一个数组 nums: [1,2,2,2,3,4],target 是 2,我们应该插入的位置是 1
  • 寻找最左,用右边逼近最左边

1.3.1 左闭右闭

  • 代码实现
int leftBound(vector<int>& nums,int target){
    //采用左闭右闭区间 --- 精简 --- 合并相等情况
    int left = 0;
    int right = nums.size()-1;
    while(left <= right){
        int mid = left + ((right - left) >> 1);
        if(nums[mid] >= target)right = mid - 1;
        else left = mid + 1;
    }
    //结束时,left == right + 1,[right,right+1(left)],需要检查是否越界
    //left<=right,最大就是right + 1 = nums.size()
    if(left == num.size() || nums[left] != target)return -1;
    return left;
}

1.3.2 左闭右开

  • 代码实现
int leftBound(vector<int>& nums,int target){
    //采用左闭右开区间 --- 精简 --- 合并相等情况
    int left = 0;
    int right = nums.size();
    while(left < right){
        int mid = left + (right - left) / 2;
        if(nums[mid] >= target)right = mid;
        else left = mid + 1;
    }
    //结束时,[left,right],left == right
    //target很大,left一直右移
    if(left == nums.size() || nums[left] != target)return -1;
    return left;
}

1.4 寻找最右满足条件的边界

1.4.1 左闭右闭

  • 代码实现
int rightBound(vector<int>& nums,int target){
    //采用左闭右闭区间
    int left = 0;
    int right = nums.size() - 1;
    while(left <= right){
        int mid = left + (right - left) / 2;
        //最右,左边逼近
        if(nums[mid] <= target)left = mid + 1;
        else right = mid - 1;
    }
    //结束时,[right, left],left == right+1;
    //1、分析返回值,return left - 1 / right;
    //2、分析越界情况,left->[0,nums.size()],left -1 ->[-1,nums.size()-1]
    if(left - 1 < 0 || nums[left - 1] != target)return -1;
    return left - 1;//return right;
}

1.4.2 左闭右开

  • 代码实现
int rightBound(vector<int>& nums,int target){
    //采用左闭右开区间
    int left = 0;
    int right = nums.size();
    while(left < right){
        int mid = left + (right - left) / 2;
        if(nums[mid] <= target)left = mid + 1;
        else right = mid;
    }
    if(left - 1 < 0 || nums[left - 1] != target)return -1;
    //结束时,[left,right]left == right,
    return left - 1;//return right - 1;
}
  • 为什么返回left/right-1?

image-20220826115933115

  • 最后的判断处理

image-20220826120125623

  • 小结:
  • 1、判断返回值,通过nums[mid] == target情况判断,分析left/right与mid的关系;
  • 2、判断越界,分析left,right的取值范围;

1.5 寻找最左插入位置

  • 找不到不应该返回-1,而是返回应该插入的位置;

1.5.1 左闭右闭

  • 代码实现
void leftInsert(vector<int>& nums,int target){
    //采用左闭右闭区间,右边逼近左边
    int left = 0;
    int right = nums.size() - 1;
    while(left <= right){
        int mid = left + (right - left) / 2;
        if(nums[mid] >= target)right = mid - 1;
        else left = mid + 1;
	}
    //结束:[right,left(right + 1)],left = right + 1
    //mid = right + 1 = left
    //范围:left->[0,nums.size()],不需要进行区间,相等检查
    return left;//return right + 1;
}

1.5.2 左闭右开

  • 代码实现
void leftInsert(vector<int>& nums,int target){
    //采用左闭右开区间,右边逼近左边
    int left = 0;
    int right = nums.size();
    while(left < right){
        int mid = left + (right -left) / 2;
        if(nums[mid] >= target)right = mid;
        else left = mid + 1;
    }
    //结束,right = left,[left,right],
    //mid = right;left->[0,nums.size()]
    return left;//return right;
}

1.6 寻找最右插入位置

1.6.1 左闭右闭区间

  • 代码实现
void rightInsert(vector<int>& nums,int target){
    //采用左闭右闭,左边逼近右边
    int left = 0;
    int right = nums.size() - 1;
    while(left <= right){
        int mid = left + (right - left) / 2;
        if(nums[mid] <= target)left = mid + 1;
        else right = mid - 1;
    }
    //结束,[right,left(right + 1)],left = right + 1;
    //mid = left - 1 = right;left->[0,nums.size()],
    //left-1 -> [-1,nums.size()-1],注意,是寻找右边插入位置,因此+1    
    return left;//return right + 1
}

1.6.2 左闭右开区间

  • 代码实现
void rightInsert(vector<int>& nums,int target){
    //采用左闭右开区间,左边逼近右边
    int left = 0;
    int right = nums.size();
    while(left < right){
        int mid = left + (right - left) / 2;
        if(nums[mid] <= target)left = mid + 1;
        else right = mid;
    }
    //结束,[left,right],left == right, mid = left - 1;
    //left->[0,nums.size()]
    return left;//return right;
}
<think>好的,我现在需要解决用户关于二分法边界条件处理的问题。用户提到了参考的引用内容,涉及二分法的流程、中点计算以及不同情况下的边界处理。首先,我应该回顾二分法的基本概念,然后结合引用中的信息,详细分析边界条件的处理方法。 首先,二分法的关键在于确定搜索区间和终止条件。根据引用[1],区间可以是左闭右开[left, right),或者是闭区间[left, right]。不同的区间选择会影响循环条件和边界更新。例如,如果区间是左闭右开,那么终止条件应该是left < right,因为当left等于right时,区间已经没有元素了。而闭区间的话,终止条件应为left <= right,因为当left等于right时,区间还有一个元素需要检查。 接下来,中点mid的计算。引用[2]提到使用int mid = low + ((high - low) / 2);这种方法可以避免整数溢出的问题,尤其是在处理大数组时。这对应到Python的话,可能不需要担心,因为Python的int可以很大,但习惯上还是可以用类似的写法,比如mid = left + (right - left) // 2。同时,需要注意当区间长度为偶数时,mid是向下取整还是向上取整,这会影响后续的边界调整,特别是在处理左边界或右边界时。 然后,根据引用[3],二分法多种情况,比如查找特定值、左边界或右边界。每种情况下的边界调整方式不同。例如,寻找目标值的精确匹配时,当arr[mid]等于目标值时直接返回mid;但如果是寻找左边界,当等于目标值时可能需要继续向左搜索,因此调整右边界为mid,而左边界的情况则相反。 此外,循环终止后的处理也很重要。例如,在闭区间情况下,循环结束时left会大于right,这时候需要判断是否找到了目标值。如果是寻找插入位置的问题,可能需要返回left或right+1,具体取决于实际情况。 还需要考虑避免死循环的问题,特别是在更新left或right时是否应该mid+1或mid-1。比如,在左闭右闭区间中,如果arr[mid] < target,那么left应该更新为mid + 1,因为mid已经被检查过不满足条件;而如果arr[mid] > target,right更新为mid - 1。如果是左闭右开区间,当更新right时,可能需要保持右开,所以right = mid,而left更新为mid + 1。 最后,总结不同情况下的处理步骤,给出示例代码,并验证边界条件是否正确。例如,当数组长度为1时,是否能够正确处理;当目标值不在数组中时,是否能正确返回-1或插入位置。 可能遇到的问题包括:如何确保循环终止,如何处理重复元素,如何确定最终的返回值是否符合预期。需要结合具体应用场景来调整边界条件,比如寻找第一个出现的元素还是最后一个出现的元素,这时候边界的调整方式会有所不同。 总之,处理二分法边界条件需要明确区间的定义,正确计算中点,合理调整边界,并在循环结束后进行必要的检查。通过分步骤的说明和示例代码,可以帮助用户更好地理解和应用不同的处理方法。</think>### 二分法边界条件处理方法 二分法边界条件处理是确保算法正确性的关键,主要涉及**区间定义**、**循环终止条件**和**中点更新规则**三个核心要素。以下是具体处理步骤: --- #### 1. **明确区间定义** - **左闭右闭区间**:`[left, right]` 初始值:`left = 0`, `right = len(arr) - 1` 终止条件:`left > right`(即区间无元素)[^1] 适用场景:精确查找目标值或边界。 - **左闭右开区间**:`[left, right)` 初始值:`left = 0`, `right = len(arr)` 终止条件:`left == right`(右开意味着`right`本身不包含在区间内)[^3] 适用场景:插入位置或范围查询。 --- #### 2. **确定循环终止条件** - **左闭右闭区间**: ```python while left <= right: mid = left + (right - left) // 2 ``` 终止时`left > right`,表示区间已空。 - **左闭右开区间**: ```python while left < right: mid = left + (right - left) // 2 ``` 终止时`left == right`,区间`[left, right)`无元素[^3]。 --- #### 3. **中点计算与边界更新** - **中点计算**: 使用`mid = left + (right - left) // 2`避免整数溢出,且保证向下取整[^2]。 - **边界更新规则**: - **精确查找**(目标值唯一): ```python if arr[mid] == target: return mid elif arr[mid] < target: left = mid + 1 # 左闭右闭区间中,mid已检查,需排除 else: right = mid - 1 ``` - **左边界查找**(第一个等于目标的元素): ```python if arr[mid] >= target: right = mid # 保持左闭右闭或左闭右开的一致性 else: left = mid + 1 ``` 终止后需验证`left`是否越界或`arr[left] == target`[^3]。 - **右边界查找**(最后一个等于目标的元素): ```python if arr[mid] <= target: left = mid + 1 else: right = mid ``` 终止后需验证`right-1`是否为有效索引。 --- #### 4. **循环结束后的处理** - **精确查找**:若未找到,返回`-1`。 - **左边界查找**:检查`left`是否在数组范围内且`arr[left] == target`。 - **插入位置**:直接返回`left`(左闭右开区间)或`right + 1`(左闭右闭区间)。 --- #### 示例代码(左闭右闭区间) ```python def binary_search(arr, target): left, right = 0, len(arr) - 1 while left <= right: mid = left + (right - left) // 2 if arr[mid] == target: return mid elif arr[mid] < target: left = mid + 1 else: right = mid - 1 return -1 ``` --- #### 验证边界条件的典型场景 1. **空数组**:直接返回`-1`。 2. **单元素数组**:检查是否匹配目标。 3. **目标值为最小值或最大值**:确保不越界。 4. **重复元素**:左/右边界查找是否返回正确位置。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值