ACM模板 - 二分查找(完整版)

本文详细解析了二分查找的不同变种,共计64种,并提供了针对不同问题的高效实现示例,包括寻找特定元素的位置及边界条件等。

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

64种。理由如下:
对其进行分类:
取整方式:向下取整 向上取整 (共2种)
区间开闭:闭区间 左闭右开区间 左开右闭区间 开区间 (共4种)
问题类型:
对于不下降序列a,求最小的i,使得a[i] = key
对于不下降序列a,求最大的i,使得a[i] = key
对于不下降序列a,求最小的i,使得a[i] > key
对于不下降序列a,求最大的i,使得a[i] < key
对于不上升序列a,求最小的i,使得a[i] = key
对于不上升序列a,求最大的i,使得a[i] = key
对于不上升序列a,求最小的i,使得a[i] < key
对于不上升序列a,求最大的i,使得a[i] > key
(共8种)
综上所述,二分查找共有 2*4*8=64 种写法。

算法的目的是解决问题,下面以针对不下降序列a的4个问题为例,给出我认为效率较高,较为简洁的代码。

对于不下降序列a,n为序列a元素的个数,key为关键字:

1.求最小的i,使得a[i] = key,若不存在,则返回-1
int binary_search_1(int a[], int n, int key)
{
    int m, l = 0, r = n - 1; // 闭区间[0, n - 1]
    while (l < r)
    {
        m = l + ((r - l) >> 1); // 向下取整
        if (a[m] < key) l = m + 1;
        else r = m;
    }
    if (a[r] == key) return r;
    return -1;
}

2.求最大的i,使得a[i] = key,若不存在,则返回-1

int binary_search_2(int a[], int n, int key)
{
    int m, l = 0, r = n - 1; // 闭区间[0, n - 1]
    while (l < r)
    {
        m = l + ((r + 1 - l) >> 1); // 向上取整
        if (a[m] <= key) l = m;
        else r = m - 1;
    }
    if (a[l] == key) return l;
    return -1;
}

3.求最小的i,使得a[i] > key,若不存在,则返回-1

int binary_search_3(int a[], int n, int key)
{
    int m, l = 0, r = n - 1;//闭区间[0, n - 1]
    while (l < r)
    {
        m = l + ((r - l) >> 1);//向下取整
        if (a[m] <= key) l = m + 1;
        else r = m;
    }
    if (a[r] > key) return r;
    return -1;
}

4.求最大的i,使得a[i] < key,若不存在,则返回-1

int binary_search_4(int a[], int n, int key)
{
    int m, l = 0, r = n - 1;//闭区间[0, n - 1]
    while (l < r)
    {
        m = l + ((r + 1 - l) >> 1);//向上取整
        if (a[m] < key) l = m;
        else r = m - 1;
    }
    if (a[l] < key) return l;
    return -1;
}

实际上,对于3、4,也可以先判断解是否存在,再进行二分查找。

显然,上面的代码还不够简洁。下面给出我认为最简洁的代码:

// 1.
int ans = std::lower_bound(a, a + n, key) - a;
ans = (ans == n || a[ans] != key) ? -1 : ans;

// 2.
int ans = std::upper_bound(a, a + n, key) - a;
ans = (ans == 0 || a[ans - 1] != key) ? -1 : ans - 1;

// 3.
int ans = std::upper_bound(a, a + n, key) - a;
ans = (ans == n) ? -1 : ans;

// 4.
int ans = std::lower_bound(a, a + n, key) - a;
ans = (ans == 0) ? -1 : ans - 1;
<think>好的,用户想要一个通用的二分法算法模板和实现代码。首先,我需要回顾一下二分查找的基本原理和常见问题。根据用户提供的引用,二分查找的关键在于二段性,而不仅仅是数组有序。这点很重要,可能用户之前认为必须有序,但其实只要满足某种单调性或分界条件就可以用二分。 接下来,用户提到了左右侧查找的需求,比如找到最左或最右边的目标值。引用[4]中给出了右闭左开的例子,但用户可能想要更通用的模板。我需要结合这些引用中的信息,总结出两种常见情况:寻找左边界和右边界,以及基本的查找。 然后,考虑模板的结构。通常二分查找有左闭右闭和左闭右开两种写法,这里需要统一处理。根据引用[4]中的代码,左右边界的处理需要调整mid的计算和左右指针的移动。比如,当找到目标值时,左边界需要继续向左搜索,而右边界则向右搜索。 另外,用户可能需要代码示例,所以得选择一种编程语言,比如Python,因为它比较通用。同时,要确保代码正确处理边界条件,避免死循环,比如在左闭右开区间时,right的初始值应该是数组长度,而循环条件为left < right。 然后,模板需要包括三个部分:基本二分、左边界和右边界查找。每个部分的循环条件、mid计算、指针更新都需要明确。例如,基本二分在找到目标时直接返回,而左右边界查找则继续缩小区间,直到锁定边界。 还要考虑用户可能的疑问,比如如何处理找不到目标的情况,是否需要返回值检查。在代码中添加注释说明返回值的含义,比如左边界返回第一个等于target的位置,找不到则返回-1。 最后,根据用户提供的引用,特别是引用[1]和[4],需要强调二段性的概念,说明模板的适用场景不仅限于有序数组,而是满足特定条件的情况。这可能帮助用户更好地应用模板到不同的问题中。 总结步骤:先介绍通用模板的结构,分三种情况;然后给出Python代码示例,确保语法正确;接着解释每个模板的使用场景和细节;最后根据引用材料说明二段性的重要性,并生成相关问题。</think>### 通用二分法算法模板详解 #### 一、通用二分法核心思想 二分法的本质是通过**二段性**确定搜索范围,而非必须有序数组[^1]。通过比较中间值与目标值的关系,每次将搜索空间缩小一半,时间复杂度为$O(\log n)$[^2]。 #### 二、通用模板分类(Python实现) 以下为三种典型场景的代码模板,涵盖**基本查找、左边界查找、右边界查找**: ```python # 1. 基本二分查找(精确匹配) def binary_search(nums, target): left, right = 0, len(nums) - 1 # 闭区间[left, right] while left <= right: mid = left + (right - left) // 2 # 避免整数溢出 if nums[mid] == target: return mid # 找到目标直接返回 elif nums[mid] < target: left = mid + 1 else: right = mid - 1 return -1 # 未找到 # 2. 左边界查找(第一个>=target的位置) def left_bound(nums, target): left, right = 0, len(nums) # 左闭右开区间[left, right) while left < right: mid = left + (right - left) // 2 if nums[mid] < target: left = mid + 1 else: right = mid # 保持左闭右开特性 return left if left < len(nums) and nums[left] == target else -1 # 3. 右边界查找(最后一个<=target的位置) def right_bound(nums, target): left, right = 0, len(nums) while left < right: mid = left + (right - left) // 2 if nums[mid] > target: right = mid else: left = mid + 1 # 向右压缩搜索空间 return left - 1 if left > 0 and nums[left-1] == target else -1 ``` #### 三、模板选择与使用场景 1. **基本查找**:适用于**存在唯一目标值**的场景,如$[1,3,5,7]$中找5 2. **左边界查找**:适用于处理重复元素或**寻找最小值**,如$[2,2,3,4]$中找第一个2 3. **右边界查找**:适用于寻找最大值或**确定范围上限**,如$[1,2,2,3]$中找最后一个2 #### 四、关键细节说明 1. **循环终止条件**:闭区间用`left <= right`,左闭右开区间用`left < right` 2. **中间值计算**:使用`mid = left + (right - left) // 2`防止整型溢出 3. **边界收缩**:保持区间不变量(如左闭右开区间始终维持`[left, right)`特性) 4. **返回值验证**:最终需检查下标有效性及元素匹配性[^4] #### 五、二段性扩展应用 当数组满足**某个分界条件**时即可使用二分,例如: - 寻找旋转排序数组中的最小值(如$[4,5,6,1,2]$) - 山脉数组找峰值(如$[1,3,5,4,2]$) - 寻找缺失数字等非显式有序问题[^1] $$ \text{二段性条件}: \exists x_0, \forall x < x_0 \text{满足}P(x), \forall x \ge x_0 \text{不满足}P(x) $$
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dream_Weave

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值