【问题标题】:generate top k values生成前 k 个值
【发布时间】:2011-04-09 02:31:42
【问题描述】:

我有一个问题,我想确定我是否做得最有效。我有一个大小为 N 的浮点值数组 A。这些值都在 0 和 1 之间。

我必须找到前 k 个值,它可以是 A 中最多三个数字的乘积。所以,前 k 个列表可以 有来自 A 的单个数字、两个数字的乘积或来自 A 的三个数字的乘积。

所以,这就是我现在的做法。我可以在 O(Nlogk) 时间内按降序获得前 k 个数字。然后我创建一个 max-heap 并用最大大小为 3 的最佳值初始化它,即如果我将 k 值的排序数组(降序)表示为 B 以及该数组中的索引数字,我插入索引为 (0)、(0,1) 和 (0,1,2) 的数字。接下来,我在堆上执行提取和 每当我提取大小 z(z 数字的乘积)值时,我将其替换为下一个可能大小 z 数字的集合,即 如果假设(2,4)被提取,我可以用(3,4)和(2,5)替换它。并提取k次得到结果。

如果有的话,需要更好的想法。 谢谢大家。

【问题讨论】:

  • 您确定没有将相同的数字添加两次吗?例如,(1,3) 位于 (1,2) 和 (0,3) 之后
  • 对,我没有提到它,但我们必须确保我们不会添加两次,可能使用哈希表
  • @user:由于您的数字都在 0 和 1 之间,因此您的集合中任意两个数字的乘积都将小于这两个数字(如果其中一个为 1,则等于较小的数字, 如果两者都是 1),则等于两者。因此,前 k 个将始终是单个数字。这是正确的,还是我误解了?
  • 是的,两个数字 a,b 的乘积将小于单个数字。但是,a*b 可以大于 c,d,.... (a>b>c>d>....)
  • 可能是一个愚蠢的疑问,但仍然支持 Space_C0wb0y 的评论。如果特定数字是 2 或 3 个单独数字的乘积,我们是否真的需要制表,如果列表可以包含也可以包含单独数字的前 k 个:-D

标签: algorithm computer-science


【解决方案1】:

如果我理解正确,您需要找到 k 个最大数字,这些数字可以通过将列表中的 1、2 或 3 个元素相乘得到,并且所有值都是 0 到 1 之间的浮点数。

很明显,您只需要考虑列表中的 k 个最高数字。其余的可以直接丢弃。您可以使用 O(n log k) 算法来获取它们,再次按排序顺序(我假设您的列表没有预先排序)。为了简化问题,您现在可以取它们的对数并尝试最大化数字的总和,而不是最大化乘积的原始问题。这可能会加快一点。

现在(考虑到对数表示),你所有的数字都是负数,所以把它们加在一起只会产生越来越多的负数。

让我们将 k 个最高的数字称为 A1...Ak。我们现在可以进一步减少问题,假设还存在数字 A0,它在对数表示中的值为 0,在原始表示中为 1;那么问题是用 x ≥ 的约束枚举前 k 个 3 元组({A0,...,Ak} 中的 x,y,z)。 y ≥ z 且 z

我们使用原始公式中的最大堆;我们将三元组推入堆中,使用它们的总和 (S[...]) 作为排序键。该算法首先将 [0,0,0] 推入堆。那么:

answer = []
for m in 0 .. k:
  top = heap.pop()
  answer.append(sum(top))
  (i,j,n) = top # explode the tuple
  if (n < k - 1):
      heap.push((i,j,n+1))
  if (j == n):
      heap.push((i,j+1,j+1))
      if (i == j):
          heap.push((i+1,i+1,i+1))

最后,答案包含k + 1个元素,其中第一个是[0,0,0],必须丢弃。

让给定为 -1、-3、-8、-9。然后算法是这样进行的:

Heap
Top          Rest (shown in order)

[ 0, 0, 0] | 
[ 0, 0,-1] | [ 0,-1,-1] [-1,-1,-1]
[ 0,-1,-1] | [-1,-1,-1] [ 0,-1,-3] [ 0,-3,-3]
[-1,-1,-1] | [-1,-1,-2] [ 0,-1,-3] [-1,-2,-2] [-2,-2,-2] [ 0,-3,-3]
[-1,-1,-2] | [ 0,-1,-3] [-1,-1,-3] [-1,-2,-2] [-2,-2,-2] [ 0,-3,-3]
[ 0,-1,-3] | [-1,-1,-3] [ 0,-1,-4] [-1,-2,-2] [-2,-2,-2] [ 0,-3,-3]
[-1,-1,-3] | [ 0,-1,-4] [-1,-1,-4] [-1,-2,-2] [-2,-2,-2] [ 0,-3,-3]
[ 0,-1,-4] | [-1,-2,-2] [-1,-1,-4] [ 0,-1,-5] [-2,-2,-2] [ 0,-3,-3]
...
etc.

这个算法的好处是它不枚举重复,堆大小为 O(k);要了解原因,请观察该算法在每次迭代时添加堆上的最大元素(通常更少),因此在 k 次迭代之后,堆中的元素不能超过 2k。

这给出了运行时间 O(n log k + k log k) = O((n + k) log k)。

【讨论】:

  • 嗯,迭代算法中可能存在错误......它仍然可以是线性时间,但迭代顺序并不是那么简单,因为可能是有序的 (a1,a1), (a1,a2 ), (a1,a3), (a2,a2), (a1,a4) ---我明天会更新答案
  • 是的,我也是这么想的……可能没有堆就不行了,但我喜欢日志优化
  • 好的,我重写了它以使用堆......如果你在弹出(2 ,4),然后当您下一次弹出 (2,5) 时,您将添加 (3,5) 和 (2,6)。这是次优的,您不需要在堆中有 (3,5),因为 (3,4) 已经存在并且当您现在弹出 (3,4) 时,您将重新生成后继元素 (3, 5)。然后你需要有另一个数据结构来检测重复。
  • 非常感谢。还有一个问题:如果我说我们被允许在任何一个总和(或乘积)中只取一个元素一次,它将如何改变你的算法。所以在上面给出的例子中,输出顺序应该是:-1, -3, (-1+-3), -8, (-1+-8)(or -9)
  • 嗯,最简单的改变就是扔掉那些元组 :) 第二个最简单的方法是改变将新元组添加到堆中的规则,即添加 (i,j+1,j+ 2) 代替 (...,j+1) 并添加 (i+1,i+2,i+3) 代替 (i+1,i+1,i+1);这将导致具有多个条目的元组被跳过。
【解决方案2】:

我当然看到了您可以进行的优化。

Let M be the highest number from A.
Let M2 be M * M.
Let setMM2 consist of all x from A such that M2 < x < M
If size(setMM2) >= k, 
    then your top-k consist of the highest k elements.
Else
    all x in setMM2 are in your top-k and your search becomes smaller

您可以使用 max(secondHighestNumber^2,M^3) 重复此方法并推广算法。

【讨论】:

  • 如果我们谈论快速算法,我们也应该给出一个预期的运行时间,不是吗? :)
  • @duedl0r 据我所知,这不会给您保证复杂性的改进,因为最坏的情况是 setMM2 是空的,而您又回到了开始的地方。由此带来的性能提升完全取决于数据。 但是根据我使用此类算法的经验,当您在复杂性方面获得非保证的改进时,按照与建议相同的思路进行思考可以引导您保证复杂性的改进。我希望这种(可能很大或很小)提升可以为 OP 找到更好的解决方案打开大门。
【解决方案3】:

kN因为数字是从 0 到 1,你使用的数字越多,它就越糟糕,问题就是大 k,例如 k=N^2

首先尝试单个数字,然后在堆中推送。 O(N*Log(k))

比使用堆中的这个数字并用 2 个数字创建另一个堆 B => O(k*log(k)) 最坏的情况是,但如果你对数字进行排序以防 k>N

然后你有 2 个数字和产品,然后尝试从堆 B 中创建第三个堆 C,就像你为 B 做的那样,但是从更大的堆。

我认为这将使 O(k*log(k))

【讨论】:

  • 这并不比我建议的好
  • 我做了另一个复杂性假设
猜你喜欢
  • 1970-01-01
  • 2016-02-04
  • 2021-03-04
  • 2013-03-15
  • 1970-01-01
  • 2012-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多