题目解析与详解
问题背景与基本概念
在这道题中,我们需要模拟一个集合的操作并找到集合的 MEX 值。MEX 是一种数学概念,其定义是集合中最小的未出现的非负整数。举几个简单的例子来帮助理解:
- 如果集合为 ({0, 1, 2, 4}),则 MEX 是 (3)(因为 (3) 是第一个未出现在集合中的非负整数)。
- 如果集合为空集,则 MEX 是 (0)。
- 如果集合为 ({1, 2, 3}),则 MEX 是 (0)。
在本题中,初始集合为空集,我们需要执行一系列的操作,每次将区间 ([l, r]) 内的整数加入集合中,然后输出当前集合的 MEX 值。
输入与输出要求
- 输入:
- 一个整数 (q) 表示操作的次数。
- 一个二维数组
queries,其中每个子数组表示一个闭区间 ([l, r])。
- 输出:
- 一个长度为 (q) 的数组,表示每次操作后集合的 MEX。
任务分析
对于每个操作,我们需要将区间 ([l, r]) 的数字加入集合中,然后计算当前集合的 MEX。由于集合可能会动态增长,因此我们需要高效地管理集合的内容,同时快速计算出 MEX。
解题思路
方法一:使用布尔数组模拟集合
为了高效地管理整数集合,我们可以使用布尔数组 present:
present[x] = true表示数字 (x) 已经加入集合;- 否则,表示数字 (x) 不在集合中。
这样做的优点是可以快速标记和检查一个数字是否存在于集合中。与直接使用集合结构(如 HashSet)相比,布尔数组更适合处理大范围的连续整数。
具体的步骤如下:
- 初始化一个布尔数组
present,大小足够大(例如 100,001),表示可能的数字范围。 - 遍历每次操作的区间 ([l, r]),将所有整数 (l, l+1, \dots, r) 标记为
true。 - 动态更新集合的最大值 (maxNumber),以便限制计算 MEX 的搜索范围。
- 从 0 开始查找 MEX,只需找到第一个值为
false的位置即可。
MEX 的计算
- MEX 的定义是集合中最小的未出现非负整数。
- 因此,从 (0) 开始检查
present数组,只要当前数字的标记为true,就继续向上查找。 - 查找可以限制在 (maxNumber) 范围内,从而减少不必要的遍历。
时间与空间复杂度
- 时间复杂度:
- 遍历区间 ([l, r]) 的复杂度是 (O(r - l + 1))。
- 查找 MEX 的复杂度是 (O(maxNumber))。
- 总体复杂度为 (O(T)),其中 (T) 是所有操作涉及的数字总和。
- 空间复杂度:
- 需要一个布尔数组
present,大小为最大可能的数字范围(假设为 100,001),因此空间复杂度为 (O(U)),其中 (U) 是最大可能的数字。
- 需要一个布尔数组
代码实现
以下是代码的详细实现和注释:
import java.util.Arrays;
public class Main {
/**
* 求每次操作后的集合 MEX
*
* @param q 操作次数
* @param queries 每次操作的区间
* @return 每次操作后的 MEX
*/
public static int[] solution(int q, int[][] queries) {
// 假设最大可能的数字为 100000
boolean[] present = new boolean[100001];
int maxNumber = -1; // 当前集合中的最大数字
int[] result = new int[q]; // 用于存储每次操作后的 MEX
// 遍历每次操作
for (int i = 0; i < q; i++) {
int l = queries[i][0]; // 当前操作区间的左端点
int r = queries[i][1]; // 当前操作区间的右端点
// 标记 [l, r] 区间的每个整数为存在于集合中
for (int num = l; num <= r; num++) {
if (!present[num]) { // 仅标记尚未出现的数字
present[num] = true;
}
// 更新集合的最大值
if (num > maxNumber) {
maxNumber = num;
}
}
// 找到当前集合的 MEX
int mex = 0;
while (mex <= maxNumber && present[mex]) {
mex++; // 继续寻找未出现的最小非负整数
}
// 记录当前的 MEX
result[i] = mex;
}
return result;
}
public static void main(String[] args) {
// 测试用例
System.out.println(Arrays.equals(solution(4, new int[][] { { 1, 3 }, { 7, 8 }, { 0, 5 }, { 3, 6 } }),
new int[] { 0, 0, 6, 9 })); // 输出 true
System.out.println(
Arrays.equals(solution(3, new int[][] { { 0, 2 }, { 3, 4 }, { 6, 10 } }), new int[] { 3, 5, 5 })); // 输出 true
System.out.println(Arrays.equals(solution(2, new int[][] { { 2, 5 }, { 7, 9 } }), new int[] { 0, 0 })); // 输出 true
}
}
代码解析与关键点
-
初始化布尔数组 布尔数组
present被用来模拟集合,数组大小为 100,001,可以表示 0 到 100,000 的所有数字。通过这种方法,我们可以快速判断某个数字是否已经加入集合。 -
遍历操作区间 每次操作的区间 [l, r] 被逐个标记为
true。在此过程中,我们会动态更新当前集合的最大值maxNumber,以减少后续查找 MEX 的范围。 -
查找 MEX 从 0 开始遍历布尔数组,找到第一个值为
false的位置即为 MEX。由于我们提前记录了maxNumber,只需检查到maxNumber即可,无需遍历整个数组。
优化与改进
动态维护 MEX:
通过一个指针 currentMEX 记录上一次的 MEX 值,每次只从该指针开始查找。这样可以避免重复检查已经确认的部分。
更紧凑的数据结构:
如果输入数字范围非常大,可以使用 TreeSet 代替布尔数组。TreeSet 能动态维护集合中的最小未出现元素,但会增加一些操作开销。
通过以上方法,可以在处理超大数据范围时进一步优化性能。