寻找最小非负整数| 豆包MarsCode AI刷题

151 阅读6分钟

问题描述

小C拿到了一个空集。她准备进行以下操作:将 [1, r] 区间的每个整数添加进集合。每次操作后,输出当前集合的 mex。定义集合的 mex 为:集合中最小的未出现的非负整数。

解题思路

  1. 理解 mex 的定义mex 是集合中最小的未出现的非负整数。初始时,集合为空,mex 为 0。
  2. 逐步添加元素:每个查询 [start, end] 都会将区间 [start, end] 内的所有整数添加到集合中。
  3. 计算 mex:在每次添加操作后,从 0 开始检查,直到找到第一个未在集合中出现的整数,这个整数即为当前的 mex

在具体实现中,我们需要处理以下几个核心步骤:

  • 初始化空集:创建一个空的集合 current_set 来存储当前已添加的整数。
  • 处理查询:每次查询将区间内的所有整数添加到集合中。
  • 查找 mex:在每次查询后,遍历非负整数,找到第一个未在集合中出现的整数。

算法设计

  1. 初始化

    • 创建一个空集合 current_set 和一个空列表 result,用于存储每次操作后的 mex
    • 这两个数据结构分别用于记录当前集合中的元素和存储每次查询的结果。
  2. 处理每个查询

    • 对于每个查询 [start, end],使用一个 for 循环将区间 [start, end] 内的所有整数添加到 current_set 中。
    • 使用 while 循环从 0 开始检查,找到第一个未在 current_set 中出现的整数,将其添加到 result 列表中。
  3. 返回结果

    • 最后返回 result 列表,该列表包含了每次查询后的 mex 值。

代码如下:

def solution(q: int, queries: list) -> list:
    result = []
    current_set = set()
    for query in queries:
        start, end = query
        for i in range(start, end + 1):
            current_set.add(i)
        mex = 0
        while mex in current_set:
            mex += 1
        result.append(mex)
    return result

**

代码分析

  • 初始化部分

    result = []
    current_set = set()
    

    **

    这里创建了一个空列表 result 用于存储每次查询后的 mex 值,以及一个空集合 current_set 用于记录当前已添加的整数。

  • 处理每个查询

    for query in queries:
        start, end = query
        for i in range(start, end + 1):
            current_set.add(i)
    

    **

    这段代码遍历每个查询 [start, end],并使用一个 for 循环将区间 [start, end] 内的所有整数添加到 current_set 中。这样可以确保每次查询后集合中包含了所有已添加的整数。

  • 计算 mex

    mex = 0
    while mex in current_set:
        mex += 1
    result.append(mex)
    

    这段代码从 0 开始检查,找到第一个未在 current_set 中出现的整数,并将其添加到 result 列表中。这是一个简单但有效的查找 mex 的方法,但对于大量查询或大区间可能会比较慢。

有可能出现的问题

  1. 性能问题

    • 时间复杂度:每次查询中,将区间内的所有整数添加到集合中,时间复杂度为 O(n * (end - start + 1))。对于大量查询或大区间,这可能会非常慢。
    • 查找 mex:查找 mex 的过程使用了 while 循环,时间复杂度为 O(n),其中 n 是当前集合的大小。在最坏情况下,每次查询的 mex 值都很大,这会导致性能下降。
  2. 内存问题

    • 集合的大小:如果区间非常大,集合 current_set 可能会占用大量内存。例如,如果区间 [1, 1000000] 需要添加到集合中,内存消耗会非常大。
  3. 输入验证

    • 输入格式:代码没有进行输入验证,如果输入的 queries 格式不正确,可能会引发运行时错误。例如,如果输入的查询中包含负数或非整数,代码可能会出错。
    • 边界条件:没有处理一些边界条件,例如区间 [start, end] 中 start 大于 end 的情况。

个人的思考与改进

  1. 优化查找 mex 的过程

    • 使用有序数据结构:可以通过维护一个有序的数据结构(如 SortedList)来加速查找 mex 的过程。每次添加元素后,直接使用二分查找来找到 mex
    • 二分查找SortedList 支持二分查找,可以在 O(log n) 时间内找到 mex,从而显著提高查找效率。
  2. 减少集合的使用

    • 使用位图:可以使用一个位图(bitmap)来记录已添加的整数,位图的索引表示整数,值为 1 表示已添加,0 表示未添加。这样可以节省内存。
    • 直接访问:通过位图的索引直接访问,可以快速判断整数是否已添加,查找 mex 的时间复杂度为 O(1)
  3. 批处理查询

    • 合并区间:如果查询的数量非常大,可以考虑将多个查询合并处理,减少重复操作的次数。例如,可以先将所有查询的区间合并成一个大区间,再进行添加操作。
    • 懒惰添加:在实际应用中,可以采用懒惰添加的方式,即在需要查找 mex 时再将区间内的整数添加到集合中,从而减少不必要的操作。
  4. 输入验证和边界处理

    • 输入验证:在代码中增加输入验证,确保输入的 queries 格式正确,且所有区间内的整数都是非负整数。
    • 边界条件:处理一些边界条件,例如区间 [start, end] 中 start 大于 end 的情况,直接跳过该查询。
  5. 并行处理

    • 多线程或多进程:对于大规模数据,可以考虑使用多线程或多进程来并行处理查询,从而提高整体性能。
    • 分块处理:将查询分成多个块,每个块在一个单独的线程或进程中处理,最后合并结果。
  6. 使用更高效的数据结构

    • 线段树:线段树可以用于区间操作和查找最小值,可以在 O(log n) 时间内完成区间内的添加和查找操作。
    • 树状数组:树状数组(Fenwick Tree)也可以用于区间操作和查找最小值,适合处理大规模数据。
  7. 动态规划

    • 状态记录:可以使用动态规划的方法来记录每个查询后的 mex 值,避免每次都从 0 开始查找。
    • 状态转移:通过记录每次查询后的 mex 值,可以在下一次查询时快速找到新的 mex 

总结

采用以上的相关算法,我们可以解决题目中的问题并做出相应的改进