计算一组区间中未覆盖的最小正整数

11

几周前,有人在这里发布了这个问题,但看起来像是没有先前研究的作业,OP在得到一些负面评价后立即将其删除。

尽管如此,这个问题本身相当有趣,我已经思考了一个星期,却没有找到令人满意的解决方法。希望能有人帮忙吗?

问题如下:给定N个整数区间的列表,其边界可以取任何值从0,找到不属于任何输入区间的最小整数i

例如,如果给定列表[3,5] [2,8] [0,3] [10,13](当N=4),则该算法应返回9

我可以想到的最简单的解决方案运行时间为O(n log(n)),并由三个步骤组成:

  1. 按增加的下限对间隔进行排序
    • 如果最小下限>0,则返回0;
    • 否则重复合并第一个间隔与第二个间隔,直到第一个间隔(比如[a,b])不与第二个间隔(比如[c,d])接触——也就是说,直到b+1<c,或只剩一个间隔。
  2. 返回b + 1

这个简单的解决方案运行时间为O(n log(n)),但原始发布者写道该算法应该在O(n)中运行。如果间隔已排序,那就很容易了,但OP给出的例子包括未排序的间隔。 我猜这可能与边界有关,但我不确定是什么...哈希?线性时间排序?欢迎提出想法。


这里是上述算法的粗略Python实现:

def merge(first, second):
    (a, b), (c, d) = first, second
    if c <= b + 1:
        return (a, max(b, d))
    else:
        return False

def smallest_available_integer(intervals):
    # Sort in reverse order so that push/pop operations are fast
    intervals.sort(reverse = True)

    if (intervals == [] or intervals[-1][0] > 0):
        return 0

    while len(intervals) > 1:
        first = intervals.pop()
        second = intervals.pop()

        merged = merge(first, second)
        if merged:
            print("Merged", first, "with", second, " -> ", merged)
            intervals.append(merged)
        else:
            print(first, "cannot be merged with", second)
            return first[1] + 1

print(smallest_available_integer([(3,5), (2,8), (0,3), (10,13)]))

输出:

Merged (0, 3) with (2, 8)  ->  (0, 8)
Merged (0, 8) with (3, 5)  ->  (0, 8)
(0, 8) cannot be merged with (10, 13)
9

我假设您的意思是非负整数,对吧? - Chris Arena
这里似乎有一个错误:其边界可以取0到N³的任何值(应为1而非0)。 - Vincent
“常数时间排序?”这是不存在的。即使在最佳情况下,对于一个由 n 个值组成的序列进行排序也需要 O(n) 的时间。你必须进行至少 n-1 次比较才能检查元素是否已排序。 - Bakuriu
6
使用基于N进制的基数排序算法,可以在O(N)的时间内对范围在(0,N^3)之间的N个整数进行排序(假设每次比较都需要恒定的时间)。 - mrip
@Vincent 确实是 0 不是 1,抱歉。 - Clément
@Mrip,谢谢!我一直在考虑桶排序,这需要假设输入是均匀分布的...太棒了,你的回答。 - Clément
2个回答

8
详细阐述@mrip的评论:您可以通过改变排序算法的方式,使用您已经描述的确切算法,在O(n)时间内完成此操作。
通常,基数排序使用基数2:元素根据其位是0还是1被分为两个不同的桶。每轮基数排序需要O(n)的时间,并且最大数的每个位数都需要一轮。如果将最大数记为U,则时间复杂度为O(n log U)。
但是,您可以更改基数排序的基数。使用基数b,每轮都需要O(n+b)的时间,因为初始化和迭代桶需要O(b)的时间,将元素分配到桶中需要O(n)的时间。然后有logbU轮。这样就得到了O((n + b)logb U)的运行时间。
关键在于,由于最大数字U = n3,所以您可以设置b = n并使用基数为n的基数排序。现在,轮数为lognU=lognn3=3,每一轮都需要O(n)的时间,因此对数字进行排序的总工作量为O(n)。更普遍地说,您可以在O(kn)的时间内对区间[0,nk)内的数字进行排序,其中k为任意整数。如果k是一个固定的常数,那么时间复杂度就是O(n)。
结合您最初的算法,这将在O(n)的时间内解决问题。希望这有所帮助!

谢谢,这确实是一个非常漂亮的解决方案! - Clément

0
另一个想法是以某种方式使用这些区间的补集。假设C()为您提供区间的补集,例如C([3,5])将是小于3和大于5的整数。如果最大数字是N^3,则使用模N^3+1,您甚至可以将其表示为另一个区间[6,(N^3+1)+2]。
如果您想要一个不属于任何原始区间的数字,则该数字应出现在所有这些区间的补集中。然后,就需要编写一个函数来计算任意两个这样的“补集区间”的交集。
我还没有实现这个想法,因为我的纸笔绘图表明,在计算这样的交集时需要考虑比我最初想象的更多的情况。但我认为这背后的想法是有效的,并且它将产生一个O(n)算法。
编辑
经过进一步思考,有一种最坏情况会使事情比我最初想象的更加复杂。

如果将这些补集表示为它们自己的区间,要找到两个之间的交集,就必须考虑许多情况,以检查它们相对于彼此的位置。但是,这可以基于它们的起点和终点来完成,因此每个交集计算将需要一定的时间,这个时间不依赖于输入的长度。由于您只需要处理长度为N的输入列表中的每个项目,因此应该会得到O(N)的复杂度。 - brm
我现在意识到有一个情况比我最初想象的要复杂得多。不太确定这对复杂性有什么影响,但它可能不再是O(n)了(至少不是我想要进行交集的方式)。 - brm
我认为问题之一是两个补集的交集不是一个区间的补集;因此,交集会变得越来越复杂,最终每个交集需要O(n)次比较,因此总共需要O(n^2)次比较。 - Clément

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接