题解: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
【核心思想】
-
问题分析:给定一个升序排列的数组 \(q[0..n-1]\) 和 \(q\) 个查询,对于每个查询元素 \(x\),需要找到其在数组中的起始位置(第一个出现位置)和终止位置(最后一个出现位置)。如果 \(x\) 不存在,返回
-1 -1。这是一个二分查找的经典应用,需要分别查找左边界和右边界。 -
算法选择:
- 整数二分查找:利用数组有序性,每次将搜索范围减半
- 两次二分:第一次找左边界(第一个 \(\geq x\) 的位置),第二次找右边界(最后一个 \(\leq x\) 的位置)
-
关键步骤:
- 查找左边界(第一个 \(\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\) 即为右边界位置
- 输出左边界和右边界
- 查找左边界(第一个 \(\geq x\) 的位置):
-
时间/空间复杂度:
- 时间复杂度:\(O(q \cdot \log n)\),每次查询进行两次二分,每次二分 \(O(\log n)\)
- 空间复杂度:\(O(n)\),存储数组 \(q\)
-
整数二分查找的核心思想:
- 单调性利用:数组升序排列,满足二分查找的前提条件
- 区间收缩:每次根据中间值与目标值的大小关系,将搜索范围减半
- 边界处理:
- 查找左边界时,\(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
浙公网安备 33010602011771号