题解:AcWing 789 数的范围

【题目来源】

AcWing:789 数的范围 - AcWing题库

【题目描述】

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

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

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

【输入】

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

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

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

【输出】

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

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

【输入样例】

6 3
1 2 2 3 3 4
3
4
5

【输出样例】

3 4
5 5
-1 -1

【核心思想】

  1. 问题分析:给定一个升序排列的数组 \(q[0..n-1]\)\(q\) 个查询,对于每个查询元素 \(x\),需要找到其在数组中的起始位置(第一个出现位置)和终止位置(最后一个出现位置)。如果 \(x\) 不存在,返回 -1 -1。这是一个二分查找的经典应用,需要分别查找左边界和右边界。

  2. 算法选择

    • 整数二分查找:利用数组有序性,每次将搜索范围减半
    • 两次二分:第一次找左边界(第一个 \(\geq x\) 的位置),第二次找右边界(最后一个 \(\leq x\) 的位置)
  3. 关键步骤

    • 查找左边界(第一个 \(\geq x\) 的位置):
      • 初始化 \(l = 0, r = n - 1\)
      • 循环条件 \(l < r\),取 \(mid = (l + r) >> 1\)
      • \(q[mid] \geq x\):说明答案在左半部分,令 \(r = mid\)
      • \(q[mid] < x\):说明答案在右半部分,令 \(l = mid + 1\)
      • 循环结束后,\(l\) 即为左边界候选位置
    • 检查是否存在:若 \(q[l] \neq x\),说明 \(x\) 不存在,输出 -1 -1
    • 查找右边界(最后一个 \(\leq x\) 的位置):
      • 重新初始化 \(l = 0, r = n - 1\)
      • 循环条件 \(l < r\),取 \(mid = (l + r + 1) >> 1\)注意 \(+1\) 防止死循环
      • \(q[mid] \leq x\):说明答案在右半部分,令 \(l = mid\)
      • \(q[mid] > x\):说明答案在左半部分,令 \(r = mid - 1\)
      • 循环结束后,\(l\) 即为右边界位置
    • 输出左边界和右边界
  4. 时间/空间复杂度

    • 时间复杂度:\(O(q \cdot \log n)\),每次查询进行两次二分,每次二分 \(O(\log n)\)
    • 空间复杂度:\(O(n)\),存储数组 \(q\)
  5. 整数二分查找的核心思想

    • 单调性利用:数组升序排列,满足二分查找的前提条件
    • 区间收缩:每次根据中间值与目标值的大小关系,将搜索范围减半
    • 边界处理
      • 查找左边界时,\(mid\)\(+1\),收缩右边界时包含 \(mid\)
      • 查找右边界时,\(mid\)\(+1\),收缩左边界时包含 \(mid\),防止死循环
    • 两次二分策略:分别查找第一个 \(\geq x\) 和最后一个 \(\leq x\),覆盖所有重复元素
    • 适用于有序数组查找、范围查询、边界定位等场景

【解题思路】

【算法标签】

整数二分

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 100010;  // 定义数组最大容量

int n, m;  // n: 数组长度,m: 查询次数
int q[N];   // 存储有序数组

int main() {
    // 输入数据
    scanf("%d%d", &n, &m);  // 读取数组长度和查询次数
    for (int i = 0; i < n; i++) scanf("%d", &q[i]);  // 读取有序数组

    // 处理每个查询
    while (m--) {
        int x;
        scanf("%d", &x);  // 读取要查询的数字

        // 二分查找左边界(第一个>=x的位置)
        int l = 0, r = n - 1;
        while (l < r) {
            int mid = (l + r) >> 1;  // 取中间点
            if (q[mid] >= x) r = mid;  // 中间值>=x,搜索左半部分
            else l = mid + 1;         // 否则搜索右半部分
        }

        // 检查是否找到x
        if (q[l] != x) {
            // 没找到,输出-1 -1
            cout << "-1 -1" << endl;
        } else {
            // 找到了,输出左边界
            cout << l << " ";

            // 二分查找右边界(最后一个<=x的位置)
            l = 0, r = n - 1;
            while (l < r) {
                int mid = (l + r + 1) >> 1;  // 注意这里要+1防止死循环
                if (q[mid] <= x) l = mid;    // 中间值<=x,搜索右半部分
                else r = mid - 1;            // 否则搜索左半部分
            }

            // 输出右边界
            cout << l << endl;
        }
    }

    return 0;
}

【运行结果】

6 3
1 2 2 3 3 4
3
3 4
4
5 5
5
-1 -1
posted @ 2026-06-14 10:36  团爸讲算法  阅读(11)  评论(0)    收藏  举报