【问题标题】:Extracting 2 numbers n times and placing back the addition in O(n) instead of O(n*log(n))提取 2 个数字 n 次并将加法放回 O(n) 而不是 O(n*log(n))
【发布时间】:2012-06-21 00:21:21
【问题描述】:

我正在用我的 O(n*log(n)) 解决方案展示我的教授在课堂上展示的一个问题:

给定n 号码列表,我们希望执行以下n-1 次:

  • 从列表中提取两个最小元素x,y并呈现出来
  • 创建一个新号码z,其中z = x+y
  • z放回列表中

建议O(n*log(n))O(n) 的数据结构和算法

解决方案:

我们将使用最小堆:

只创建一次堆需要 O(n)。之后,提取两个最小元素将花费 O(log(n))。将z 放入堆中需要 O(log(n))。

执行上述n-1 次需要 O(n*log(n)),因为:

O(n)+O(n∙(logn+logn ))=O(n)+O(n∙logn )=O(n∙logn )

但是我怎么能在 O(n) 中做到呢?

编辑:

通过说:“从列表中提取两个最小元素x,y呈现它们”,我的意思是printf("%d,%d" , x,y),其中xy 是列表中的最小元素当前列表。

【问题讨论】:

  • 只是为了确保清楚:您想将数组简化为单个元素,每一步都删除 2 个最小元素并注入它们的总和,并且您希望在 O(n) 中执行此操作时间?
  • 你知道列表中数字的大小吗?它们是否保证在某个范围内?
  • 您不可能误解了这个问题,是吗? :) 我下意识的反应是说这是不可能的。显然,您需要迭代数组 (n-1) 次,这意味着您将在 O(n) 次的范围内执行 some 操作。要实现总体 O(n) 时间,您需要该操作为 O(1)。您可以在 O(1) 时间内在排序列表中找到两个最小值,但将一个值注入列表并保持排序将是 log(n)。除非你想进入基数排序,但这有点可疑。
  • +1 对于这个问题,我的好奇心被激起了。 :)
  • Cheeken 下意识反应的证明:假设你能做到这一点。进一步假设当您将z 插入到列表中时,您在其上粘贴了一个标志,表示“这是一个计算值,而不是原始值”。最后假设当你显示数字时,你只打印出未设置标志的数字。然后你已经在O(n) 中对你的数字列表进行了排序。因此,需要某种欺骗手段,例如对固定大小的整数进行基数排序。

标签: arrays algorithm list math data-structures


【解决方案1】:

这在一般情况下是不可能的。

您的问题陈述表明您必须将数组缩减为单个元素,总共执行 n-1 次缩减操作。因此,所执行的归约操作的数量在 O(n) 的数量级上。为了实现总体 O(n) 的运行时间,每个归约操作必须在O(1) 中运行。

你已经明确定义了你的归约操作:

  • 删除数组中的 2 个最小元素并打印它们,然后
  • 将这些元素的总和插入到数组中。

如果您的数据结构是一个排序列表,那么在 O(1) 时间内删除两个最小元素是很简单的(将它们从列表的末尾弹出)。但是,在 O(1) 中重新插入元素是不可能的(在一般情况下)。正如 SteveJessop 指出的那样,如果您可以在 O(1) 时间内插入排序列表,则结果操作将构成 O(n) 排序算法。但是没有这样的已知算法。

这里有一些例外。如果您的数字是整数,您可以使用“基数插入”来实现 O(1) 插入。如果您的数字数组在数轴中足够稀疏,您可以推断出 O(1) 中的插入点。还有很多其他的例外,但它们都是例外。

这个答案本身并不能回答你的问题,但我相信它足够相关,值得回答。

【讨论】:

  • 这真的只是我和其他人的cmets的转发。但鉴于似乎没有解决方案,我认为谨慎地发布作为答案。
  • 但即使使用基数排序,它也是 O(n+m),其中 m 是存储桶的数量(基数?存储桶排序 == 基数排序,但我不知道正确的术语), m >= n 因为你不知道哪些存储桶中有数据(即使它们是空的也必须全部尝试)。即使这样也没有假设负数。如果需要重新检查旧桶,O(n + m^2)
  • 请看我的回答,如果数组被链表替换,那么及时重新插入O(1) 是可行的。然而,在后台仍然潜伏着一种隐含的排序。
【解决方案2】:

这不是一个完整的答案。但如果列表已排序,那么您的问题在O(n) 中很容易解决。为此,请将所有数字排列在一个链表中。保持一个指向头部的指针,并在中间的某个地方。在每一步中,取出头部的顶部两个元素,打印它们,将中间指针前进到总和应该到达的位置,然后插入总和。

起始指针将移动接近2n 次,中间指针将移动大约n 次,并插入n。所有这些操作都是O(1),所以总和是O(n)

一般情况下,您无法及时排序O(n),但在一些特殊情况下您可以。所以在某些情况下是可行的。

当然,一般情况无法及时解决O(n)。为什么不?因为给定您的输出,及时O(n) 您可以运行程序的输出,按顺序构建成对总和列表,并将它们从输出中过滤掉。剩下的是按排序顺序排列的原始列表的元素。这将给出一个O(n) 通用排序算法。

更新:我被要求展示 你怎么能从输出 (10, 11), (12, 13), (14, 15), (21, 25) , (29, 46) 到输入列表?诀窍是你总是保持一切井井有条,然后你知道如何看。对于正整数,下一个即将使用的总和将始终位于该列表的开头。

Step 0: Start
  input_list: (empty)
  upcoming sums: (empty)

Step 1: Grab output (10, 11)
  input_list: 10, 11
  upcoming_sums: 21

Step 2: Grab output (12, 13)
  input_list: 10, 11, 12, 13
  upcoming_sums: 21, 25

Step 3: Grab output (14, 15)
  input_list: 10, 11, 12, 13, 14, 15
  upcoming_sums: 21, 25, 29

Step 4: Grab output (21, 25)
  input_list: 10, 11, 12, 13, 14, 15
  upcoming_sum: 29, 46

Step 5: Grab output (29, 46)
  input_list: 10, 11, 12, 13, 14, 15
  upcoming_sum: 75

【讨论】:

  • 啊,非常好!当然,这在教授心目中的解决方案中发挥了作用。
  • 在 Python 中可以做类似的事情吗?
  • @Akavall 在 Python 中创建链表很容易,请参阅stackoverflow.com/questions/280243/python-linked-list 中的几个示例。然而,它会有一个不好的常数,所以在实践中人们通常不会那样做。
  • 如果您考虑到排序所需的时间,这也是另一种(并且可能更直接)O(n*log(n)) 解决方案。
  • @robertking 我明白你在想什么。但是现在我们就输出达成了一致,您会看到动态生成总和、过滤掉它们并返回原始列表是多么容易。
【解决方案3】:

如果值的范围小于n,那么这可以在O(n)中解决。

1> 创建一个大小等于值范围的数组 mk 并将其初始化为全零

2> 遍历数组,在数组元素的位置增加mk的值。 即如果数组元素是 a[i] 则递增 mk[a[i]]

3) 要在每次 n-1 次操作后呈现答案,请遵循以下步骤:

有两种情况:

案例 1:所有 a[i] 都是正数

        traverse through mk array from 0 to its size
        cnt = 0
        do this till cnt doesn't equal 2
          grab a nonzero element decrease its value by 1 and increment cnt by 1
        you can get two minimum values in this way
        present them 
        now do mk[sum of two minimum]++

案例 2:a[i] 中的一些是否定的

        <still to update>

【讨论】:

    【解决方案4】:

    O(nlogn) 很简单 - 只需使用堆、陷阱或跳过列表。

    O(n) 听起来很难。

    https://en.wikipedia.org/wiki/Heap_%28data_structure%29
    https://en.wikipedia.org/wiki/Treap
    https://en.wikipedia.org/wiki/Skip_list
    

    【讨论】:

    • 使用展开树可能会非常快,但这仍然可能是 O(nlogn)。展开树很好,因为最近使用的节点会移动到树的顶部附近。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-11
    • 2014-06-13
    • 2015-08-28
    • 2011-12-12
    • 1970-01-01
    相关资源
    最近更新 更多