小C的mex查询 | 豆包MarsCode AI 刷题

174 阅读5分钟

题目解析与详解

问题背景与基本概念

在这道题中,我们需要模拟一个集合的操作并找到集合的 MEX 值。MEX 是一种数学概念,其定义是集合中最小的未出现的非负整数。举几个简单的例子来帮助理解:

  • 如果集合为 ({0, 1, 2, 4}),则 MEX 是 (3)(因为 (3) 是第一个未出现在集合中的非负整数)。
  • 如果集合为空集,则 MEX 是 (0)。
  • 如果集合为 ({1, 2, 3}),则 MEX 是 (0)。

在本题中,初始集合为空集,我们需要执行一系列的操作,每次将区间 ([l, r]) 内的整数加入集合中,然后输出当前集合的 MEX 值。

输入与输出要求

  1. 输入:
    • 一个整数 (q) 表示操作的次数。
    • 一个二维数组 queries,其中每个子数组表示一个闭区间 ([l, r])。
  2. 输出:
    • 一个长度为 (q) 的数组,表示每次操作后集合的 MEX。

任务分析

对于每个操作,我们需要将区间 ([l, r]) 的数字加入集合中,然后计算当前集合的 MEX。由于集合可能会动态增长,因此我们需要高效地管理集合的内容,同时快速计算出 MEX。


解题思路

方法一:使用布尔数组模拟集合

为了高效地管理整数集合,我们可以使用布尔数组 present

  • present[x] = true 表示数字 (x) 已经加入集合;
  • 否则,表示数字 (x) 不在集合中。

这样做的优点是可以快速标记和检查一个数字是否存在于集合中。与直接使用集合结构(如 HashSet)相比,布尔数组更适合处理大范围的连续整数。

具体的步骤如下:

  1. 初始化一个布尔数组 present,大小足够大(例如 100,001),表示可能的数字范围。
  2. 遍历每次操作的区间 ([l, r]),将所有整数 (l, l+1, \dots, r) 标记为 true
  3. 动态更新集合的最大值 (maxNumber),以便限制计算 MEX 的搜索范围。
  4. 从 0 开始查找 MEX,只需找到第一个值为 false 的位置即可。

MEX 的计算

  • MEX 的定义是集合中最小的未出现非负整数。
  • 因此,从 (0) 开始检查 present 数组,只要当前数字的标记为 true,就继续向上查找。
  • 查找可以限制在 (maxNumber) 范围内,从而减少不必要的遍历。

时间与空间复杂度

  1. 时间复杂度
    • 遍历区间 ([l, r]) 的复杂度是 (O(r - l + 1))。
    • 查找 MEX 的复杂度是 (O(maxNumber))。
    • 总体复杂度为 (O(T)),其中 (T) 是所有操作涉及的数字总和。
  2. 空间复杂度
    • 需要一个布尔数组 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
    }
}

代码解析与关键点

  1. 初始化布尔数组 布尔数组 present 被用来模拟集合,数组大小为 100,001,可以表示 0 到 100,000 的所有数字。通过这种方法,我们可以快速判断某个数字是否已经加入集合。

  2. 遍历操作区间 每次操作的区间 [l, r] 被逐个标记为 true。在此过程中,我们会动态更新当前集合的最大值 maxNumber,以减少后续查找 MEX 的范围。

  3. 查找 MEX 从 0 开始遍历布尔数组,找到第一个值为 false 的位置即为 MEX。由于我们提前记录了 maxNumber,只需检查到 maxNumber 即可,无需遍历整个数组。


优化与改进

动态维护 MEX:

通过一个指针 currentMEX 记录上一次的 MEX 值,每次只从该指针开始查找。这样可以避免重复检查已经确认的部分。

更紧凑的数据结构:

如果输入数字范围非常大,可以使用 TreeSet 代替布尔数组。TreeSet 能动态维护集合中的最小未出现元素,但会增加一些操作开销。

通过以上方法,可以在处理超大数据范围时进一步优化性能。