二分查找(整数二分)

发布时间 2023-10-11 23:40:44作者: grave_master

一、算法简介

二分法,即二分搜索法,是通过不断缩小解可能存在的范围,从而求得问题最优解的方法。

例如,如果一个序列是有序的,那么可以通过二分的方法快速找到所需要查找的元素,相比线性搜索要快不少。

此外二分法还能高效的解决一些单调性判定的问题。

  • 二分的关键不在于单调性,或者说二分的本质并不是单调性。
  • 二分的本质是能否找到一个性质使得左右两个区间的元素分别满足性质和不满足性质。
  • 二分到最后一定可以得到一个结果,lr是相同的,但是要判断是否满足题目条件。

二分算法思路非常简单,但是我们需要特别注意的是下标问题,相信很多人都会遇到二分死循环的问题,所以建议大家背一个模板,又快又准确,保证不会出错的解题。

以下介绍两个模板,区别仅在于l = mid时,mid需要加一,否则会出现死循环:

区间分为:[l, mid]和[mid + 1, r]
while (l < r)
{
    int mid = (l + r) >> 1;
    if (check(mid))	r = mid;
    else	l = mid + 1;
}
区间分为:[l, mid - 1]和[mid, r]
while (l < r)
{
    int mid = (l + r + 1) >> 1;
    if (check(mid)) l = mid;
    else	r = mid - 1;
}

二、题目描述

给定一个按照升序排列的长度为 \(n\) 的整数数组,以及 \(q\) 个查询。

对于每个查询,返回一个元素 \(k\) 的起始位置和终止位置(位置从 \(0\) 开始计数)。

如果数组中不存在该元素,则返回 -1 -1

输入格式

第一行包含整数 \(n\)\(q\),表示数组长度和询问个数。

第二行包含 \(n\) 个整数(均在 \(1\) ~ \(10000\) 范围内),表示完整数组。

接下来 \(q\) 行,每行包含一个整数 \(k\),表示一个询问元素。

输出格式

\(q\) 行,每行包含两个整数,表示所求元素的起始位置和终止位置。

如果数组中不存在该元素,则返回 -1 -1

数据范围

\(1 ≤ n ≤ 100000\)
\(1 ≤ q ≤ 10000\)
\(1 ≤ k ≤ 10000\)

输入样例:

6 3
1 2 2 3 3 4
3
4
5 

输出样例:

3 4
5 5
-1 -1 

三、原题链接

AcWing789.数的范围

四、算法思路

  • 对于第一轮找起始位置,也就是找到≥x的最小值,所以可以将区间分为左区间<x,右区间≥x
  • 第一轮二分之后要判断是否满足条件,因为有可能数组中没有这个数,这个时候就要根据题目要求进行判断。
  • 对于第二轮找终止位置,也就是找到≤x的最大值,所以可以将区间分为左区间≤x,右区间>x

五、源码

#include <iostream>

using namespace std;

const int N = 100010;

int n, q;
int a[N];

int main()
{
    cin >> n >> q;
    
    for (int i = 0; i < n; ++i) cin >> a[i];
    
    while (q -- )
    {
        int x;
        cin >> x;
        
        int l = 0, r = n - 1;
        while (l < r)
        {
            int mid = (l + r) >> 1;
            if (a[mid] >= x)    r = mid;
            else    l = mid + 1;
        }
        
        if (a[l] != x)  cout << "-1 -1" << endl;
        else
        {
            cout << l << ' ';
            int l = 0, r = n - 1;
            while (l < r)
            {
                int mid = (l + r + 1) >> 1;
                if (a[mid] <= x)    l = mid;
                else    r = mid - 1;
            }
            cout << r << endl;
        }
    }
    
    return 0;
}