【详解】彻底解决二分查找出现的死循环、边界问题

文章介绍了在编程中解决二分查找算法常常遇到的边界问题,提供了两种通用的模板来避免死循环和不清晰的解题思路。通过区分左区间不成立和左区间成立的情况,给出相应的区间更新策略,并结合LeetCode的二分查找问题实例展示了模板的使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

常犯的问题

如果考虑不清楚边界问题就会导致写代码模模糊糊,似懂非懂,每次解题就碰运气,完整默写出二分模板,对了就过,不对就换一个,总是纠结不清。
二分查找本是高效的算法,若每次都因为边界问题而导致解题的不顺畅,这显然是致命的。

以下给出一个非常通用的方法,可以避免以往的所有坑点,以后再也不担心出现死循环了hhh,篇幅不长,大家耐心往下看。
注:建议大家先了解二分查找的基本概念再往下阅读,不少文章用大量图片甚至动图来解释清楚了。

图解二分法

这里我们分成两种情况讨论,每一种情况对应一个模板,模板在后面会给到。

情况一:左区间不成立,右区间成立

在这里插入图片描述

注:ans是所求的解的下标,check是检查函数。始终明确一点,闭区间[l, r]一定是包含了ans的。

  • 当mid指向左区间时,check(mid)为false,说明解必定落在右侧区间,并且在mid上是不满足条件的,所以我们把区间更新为[mid + 1, r]。
  • 当mid指向右区间时,check(mid)为true,说明解必定落在左侧区间,此时在mid上是有可能满足条件的(mid可能会刚好落在ans上),所以我们把区间更新为[l, mid]

情况二:左区间成立,右区间不成立

在这里插入图片描述

分析过程与情况一类似。
注:ans是所求的解的下标,check是检查函数。始终明确一点,闭区间[l, r]一定是包含了ans的。

  • 当mid指向左区间时,check(mid)为true,说明解必定落在右侧区间,并且在mid上是可能满足条件的(因为mid可能会刚好落在ans上),所以我们把区间更新为[mid, r]。
  • 当mid指向右区间时,check(mid)为false,说明解必定落在左侧区间,此时在mid上是不满足条件的,所以我们把区间更新为[l, mid - 1]。

模板代码

这两个模板分别对应不同的情况,要视情况而用。

情况一:左区间不成立,右区间成立

void bSearch(int l, int r){
	while(l < r){
		int mid = l + r >> 1;
		if(check(mid)) r = mid;
		else l = mid + 1;
	}
	return l;
}

情况二:左区间成立,右区间不成立

void bSearch_2(int l, int r){
	while(l < r){
		// 加1的是因为当r - l等于1时,mid始终为l,也就是check(mid)永为true,造成死循环。
		int mid = l + r + 1 >> 1; 
		if(check(mid)) l = mid; 
		else r = mid - 1;
	}
}

一个简单例子的应用

Leetcode:704.二分查找

题目:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:

>输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

代码实现:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int l = 0, r = nums.size() - 1;
        while(l < r){
            int mid = l + r >> 1; 
            if(nums[mid] >= target) r = mid;
            else l = mid + 1;
        }
        if(nums[l] == target) return l;
        else return -1;
    }
};
<think>我们被要求详细解释二分查找算法的原理和实现方式。根据引用内容,二分查找是一种在有序数组中查找特定元素的高效算法,时间复杂度为O(log n)。其基本思想是每次将搜索范围减半,直到找到目标元素或范围为空。 原理步骤: 1. 确定数组的中间位置mid 2. 比较中间元素与目标值: - 若相等,则找到目标,返回索引 - 若目标值小于中间元素,则在左半部分继续查找 - 若目标值大于中间元素,则在右半部分继续查找 3. 重复上述过程,直到找到目标或搜索区间为空 实现时需注意: - 循环终止条件:low <= high 还是 low < high? - 中间位置计算:mid = (low+high)//2 还是 mid = low + (high-low)//2(避免溢出)? - 边界更新:low = mid+1 或 high = mid-1,避免死循环 下面用Python实现一个基本的二分查找(查找一个数): ```python def binary_search(arr, target): low, high = 0, len(arr) - 1 while low <= high: mid = low + (high - low) // 2 # 避免溢出 if arr[mid] == target: return mid elif arr[mid] < target: low = mid + 1 else: high = mid - 1 return -1 # 未找到 ``` 对于查找边界(左边界或右边界)的情况,实现略有不同。例如,查找左边界(第一个等于目标的位置): ```python def left_bound(arr, target): low, high = 0, len(arr) - 1 while low <= high: mid = low + (high - low) // 2 if arr[mid] < target: low = mid + 1 else: # 当arr[mid]>=target时,收缩右边界 high = mid - 1 # 检查low是否越界,且low位置的值是否等于target if low < len(arr) and arr[low] == target: return low return -1 ``` 右边界(最后一个等于目标的位置): ```python def right_bound(arr, target): low, high = 0, len(arr) - 1 while low <= high: mid = low + (high - low) // 2 if arr[mid] <= target: low = mid + 1 else: high = mid - 1 # 检查high是否越界,且high位置的值是否等于target if high >= 0 and arr[high] == target: return high return -1 ``` 注意:边界查找的实现中,循环结束后需要额外检查索引的有效性。 二分查找的前提是数组有序。对于无序数组,需要先排序(时间复杂度O(n log n)),因此如果只进行一次查找,直接顺序查找可能更高效;但若多次查找,排序后使用二分查找更优。 二分查找的变体较多,需根据具体问题调整。例如,在旋转有序数组中查找、查找峰值等。 参考文献: [^1] 介绍了二分查找的原理和Python实现。 [^2] 提到二分查找的基本原理和高效性(O(log n))。 [^3] 强调了二分查找的细节问题,并区分了三种常用场景。 [^4] 展示了二分查找前需要排序的例子。 §§相关问题§§ 1. 二分查找算法在什么情况下会退化为线性时间复杂度? 2. 如何处理重复元素时的二分查找? 3. 二分查找算法有哪些常见的变体?</think>### 二分查找算法原理及实现详解 #### 算法原理 二分查找(Binary Search)是一种针对**有序数组**的高效搜索算法,核心思想是**分治策略**: 1. **比较中间元素**:取数组中间位置元素 $mid$ 与目标值比较 2. **缩小搜索范围**: - 若 $target = arr[mid]$,找到目标 - 若 $target < arr[mid]$,在左半区间 $[low, mid-1]$ 继续搜索 - 若 $target > arr[mid]$,在右半区间 $[mid+1, high]$ 继续搜索 3. **重复分治**:每次将搜索范围减半,直到找到目标或区间为空 **时间复杂度**:$O(\log n)$,空间复杂度:$O(1)$ **前提条件**:数组必须有序(升序或降序)[^1][^2] #### 关键实现细节 1. **循环条件**:`while low <= high`(确保区间有效) 2. **中间值计算**:`mid = low + (high - low) // 2`(避免整数溢出) 3. **边界更新**: - 目标较小时:`high = mid - 1` - 目标较大时:`low = mid + 1` #### Python 代码实现 ```python def binary_search(arr, target): low, high = 0, len(arr) - 1 while low <= high: mid = low + (high - low) // 2 # 防溢出计算 if arr[mid] == target: return mid # 找到目标,返回索引 elif arr[mid] < target: low = mid + 1 # 目标在右半区 else: high = mid - 1 # 目标在左半区 return -1 # 未找到 ``` #### 边界查找变体 1. **左边界查找**(第一个等于目标的元素): ```python def left_bound(arr, target): low, high = 0, len(arr) - 1 while low <= high: mid = low + (high - low) // 2 if arr[mid] >= target: high = mid - 1 else: low = mid + 1 return low if low < len(arr) and arr[low] == target else -1 ``` 2. **右边界查找**(最后一个等于目标的元素): ```python def right_bound(arr, target): low, high = 0, len(arr) - 1 while low <= high: mid = low + (high - low) // 2 if arr[mid] <= target: low = mid + 1 else: high = mid - 1 return high if high >= 0 and arr[high] == target else -1 ``` #### 算法特性对比 | **操作类型** | **循环条件** | **边界更新逻辑** | **返回值验证** | |--------------|--------------|---------------------------|-------------------------| | 基本查找 | `low <= high`| 严格大小比较 | 直接返回 mid | | 左边界查找 | `low <= high`| 使用 `>=` 收缩右边界 | 检查 low 位置 | | 右边界查找 | `low <= high`| 使用 `<=` 扩展左边界 | 检查 high 位置 | > 关键提示:二分查找的难点在于**边界条件处理**,需特别注意: > - 终止条件(避免死循环) > - 索引越界检查 > - 重复元素的边界处理[^3] #### 应用场景 1. 有序数组的快速检索 2. 数值范围查询(如IP地址定位) 3. 数学问题求解(如平方根近似计算) 4. 数据库索引优化(B+树底层机制)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值