【问题标题】:Computing the smallest positive integer not covered by any of a set of intervals计算未被任何一组区间覆盖的最小正整数
【发布时间】:2013-10-10 15:59:20
【问题描述】:

几周前有人在这里发布了这个问题,但它看起来非常像没有事先研究的家庭作业,并且 OP 在获得了一些反对意见后立即将其删除。

不过,这个问题本身就很有趣,我已经考虑了一个星期,但没有找到令人满意的解决方案。希望有人可以提供帮助?

问题如下:给定N个整数区间的列表,其边界可以取从0到N³的任何值,找到最小整数i使得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
  2. 返回b + 1

这个简单的解决方案在O(n log(n)) 中运行,但原始发帖人写道,算法应该在O(n) 中运行。如果区间已经排序,这很简单,但是 OP 给出的示例包括未排序的区间。 我猜它一定与N³ 绑定有关,但我不确定是什么...散列?线性时间排序?欢迎提出想法。


这是上述算法的粗略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

【问题讨论】:

  • 我假设你的意思是非负整数,只有?
  • 这里似乎有错误:whose bounds can take any values from 0 to N³ (0 而不是 1)
  • "恒定时间排序?"没有没有这样的事情。即使在最好的情况下,对n 值序列进行排序也需要O(n) 时间。您必须至少进行n-1 比较以检查元素是否已排序。
  • 您可以在 O(N) 时间内对 (0,N^3) 范围内的 N 个整数进行排序(假设每次比较花费恒定时间),使用 base-N 基数排序。
  • @Vincent 确实,0 不是 1,抱歉

标签: python algorithm big-o time-complexity intervals


【解决方案1】:

详细说明 @mrip 的评论:您可以通过使用您描述的确切算法但更改排序算法的工作方式在 O(n) 时间内完成此操作。

通常,基数排序使用基数 2:根据元素的位是 0 还是 1,将元素分成两个不同的桶。基数排序的每一轮花费时间 O(n),每个位都有一轮数量最多。调用最大的数 U,​​这意味着时间复杂度是 O(n log U)。

但是,您可以将基数排序的基数更改为其他基数。使用基数 b,每一轮都需要时间 O(n + b),因为它需要时间 O(b) 来初始化和迭代桶,并且需要 O(n) 时间来将元素分配到桶中。然后有 logb U 轮。这给出了 O((n + b)logb U) 的运行时间。

这里的诀窍是,由于最大数 U = n3,您可以设置 b = n 并使用以 n 为底的基数排序。轮数现在是 logn U = logn n3 = 3 并且每轮需要 O(n) 时间,所以总对数字进行排序的工作是 O(n)。更一般地,您可以在 O(kn) 时间内对任何 k 范围内的数字进行排序 [0, nk)。如果 k 是一个固定常数,这是 O(n) 时间。

结合您的原始算法,这可以在 O(n) 时间内解决问题。

希望这会有所帮助!

【讨论】:

  • 谢谢,这确实是一个非常漂亮的解决方案!
【解决方案2】:

另一个想法是以某种方式使用这些间隔的补码。假设 C() 为您提供一个区间的补码,例如 C([3,5]) 将是小于 3 和大于 5 的整数。如果最大数为 N^3,则使用模 N^ 3+1 你甚至可以将其表示为另一个区间 [6,(N^3+1)+2]。

如果您想要一个不属于任何原始区间的数字,则该相同数字应出现在这些区间的所有补集中。然后归结为编写一个函数,可以计算任何两个这样的“补区间”的交集。

我还没有实现这个想法,因为我的笔和纸图表明在计算这样的交叉点时需要考虑的情况比我最初想象的要多。但我认为这背后的想法是有效的,它会产生一个 O(n) 算法。

编辑

进一步思考,有一个最坏的情况使事情比我最初想象的要复杂。

【讨论】:

  • 如果您将这些补码表示为间隔本身,要找到两者的交集,您必须考虑多种情​​况以检查它们相对于彼此的位置。但这可以根据它们的起点和终点来完成,因此每个交点计算将花费一定的时间,这与输入的长度无关。由于您只需要处理长度为 N 的输入列表中的每个项目,因此应该会产生 O(N) 复杂度。
  • 我现在意识到实际上有一个案例比我最初想象的要复杂。不完全确定这对复杂性有什么影响,但它可能不再是 O(n)(至少不是我想要的交集方式)
  • 我认为问题之一是两个补码的交集不是区间的补码;所以交叉点会变得越来越复杂,最终需要对每个交叉点进行 O(n) 次比较,因此总共需要 O(n^2)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-04-21
  • 1970-01-01
  • 2012-12-03
  • 1970-01-01
  • 2018-08-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多