k-最大的双重选择

时间:2014-08-13 08:05:30

标签: python performance algorithm math

想象一下,你有两个麻袋(AB)分别有NM个球。每个球都有一个已知的数值(利润)。你被要求提取(有替换)这对球的最大总利润(由所选球的乘法给出)。

最佳提取是显而易见的:从A以及B中选择最有价值的球。

当您被要求提供第二或第k个最佳选择时,问题就出现了。按照上一种方法,您应该从AB中选择最有价值的球,而不重复选择。

这可以笨拙地解决,计算每个可能的选择的值,排序和排序(例如在python中):

def solution(A,B,K):
    if K < 1:
        return 0
    pool = []
    for a in A:
        for b in B:
            pool.append(a*b)
    pool.sort(reverse=True)
    if K>len(pool):
        return 0
    return pool[K-1]

这是有效的,但最糟糕的时间复杂度是O(N*M*Log(M*M)),我打赌有更好的解决方案。

我根据表格找到了一个解决方案,其中AB元素从较高值到较低值排序,并且这些值中的每一个都关联了一个索引,该索引表示要从另一列测试的下一个值。最初这个表看起来像:

enter image description here

A中的第一个元素是25,必须针对index 2 select from b = 0对其进行测试(20),因此25*20=500是第一个最佳选择,在增加要检查的索引之后,表格将更改为:

enter image description here enter image description here

使用这些索引,我们可以迅速获得最佳候选人选择:

25 * 20 = 500 #first from A and second from B
20 * 20 = 400 #second from A and first from B

我尝试编写此解决方案的代码:

def solution(A,B,K):
    if K < 1:
        return 0
    sa = sorted(A,reverse=true)
    sb = sorted(B,reverse=true)

    for k in xrange(K):
        i = xfrom
        j = yfrom
        if i >= n and j >= n:
                ret = 0
                break
        best = None
        while i < n and j < n:
                selected = False
                #From left
                nexti = i
                nextj = sa[i][1]
                a = sa[nexti][0]
                b = sb[nextj][0]
                if best is None or best[2]<a*b:
                        selected = True
                        best = [nexti,nextj,a*b,'l']
                #From right
                nexti = sb[j][1]
                nextj = j
                a = sa[nexti][0]
                b = sb[nextj][0]
                if best is None or best[2]<a*b:
                        selected = True
                        best = [nexti,nextj,a*b,'r']
                #Keep looking?
                if not selected or abs(best[0]-best[1])<2:
                        break
                i = min(best[:2])+1
                j = i
                print("Continue with: ", best, selected,i,j)
        #go,go,go
        print(best)
        if best[3] == 'l':
            dx[best[0]][1] = best[1]+1
            dy[best[1]][1] += 1
        else:
            dx[best[0]][1] += 1
            dy[best[1]][1] = best[0]+1
        if dx[best[0]][1]>= n:
            xfrom = best[0]+1
        if dy[best[1]][1]>= n:
            yfrom = best[1]+1
        ret = best[2]

    return ret

但它对于在线Codility法官不起作用(我是否提到这是部分解决了已经过期的Codility挑战?Sillicium 2014)

我的问题是:

  • 第二种方法是未完成的良好解决方案吗?如果是这样的话,我可能会遗漏任何线索吗?
  • 你知道对这个问题采取更好的方法吗?

2 个答案:

答案 0 :(得分:2)

您需要维护一个优先级队列。

您从(sa[0], sb[0])开始,然后转到(sa[0], sb[1])(sa[1], sb[0])。如果(sa[0] * sb[1]) > (sa[1] * sb[0]),我们可以说一下(sa[0], sb[2])(sa[1], sb[0])的比较大小吗?

答案是否定的。因此,我们必须维护一个优先级队列,并在删除每个(sa[i], sb[j])后(sa[i] * sb[j]是队列中最大的),我们必须添加到优先级队列(sa[i - 1], sb[j])和{{1} },并重复此(sa[i], sb[j - 1])次。

顺便说一下,我把这个算法作为answer to a different question。该算法最初看起来可能不同,但基本上它解决了同样的问题。

答案 1 :(得分:1)

我不确定我是否理解&#34;更换&#34;位...

...但假设这实际上与"How to find pair with kth largest sum?"相同,那么解决方案的关键是考虑所有总和(或在你的情况下是产品)的矩阵S,由A和B(一旦它们被排序) - 这个paper(由@EvgenyKluev引用)给出了这个线索。

(你想要A * B而不是A + B ......但答案是一样的 - 虽然负数复杂但我认为不会使方法无效。)

示例显示了正在发生的事情:

  for A = (2, 3, 5, 8, 13)
  and B = (4, 8, 12, 16)

我们有(名词)数组S,其中S [r,c] = A [r] + B [c],在这种情况下:

   6 ( 2+4),  10 ( 2+8),  14 ( 2+12),  18 ( 2+16)
   7 ( 3+4),  11 ( 3+8),  15 ( 3+12),  19 ( 3+16)
   9 ( 5+4),  13 ( 5+8),  17 ( 5+12),  21 ( 5+16)
  12 ( 8+4),  16 ( 8+8),  20 ( 8+12),  14 ( 8+16)
  17 (13+4),  21 (13+8),  25 (13+12),  29 (13+16)

(正如参考文献指出的那样,我们不需要构造数组S,我们可以在需要时生成S中项目的值。)

非常有趣的事情是S的每一列都按升序包含值(当然),因此我们可以通过合并列来从S中按降序提取值(读取从底部)。

当然,可以使用优先级队列(堆)来完成合并列 - 因此max-heap解决方案。最简单的方法是使用S的底行开始堆,用它来自的列标记每个堆项。然后弹出堆的顶部,并从刚弹出的列中推出下一个项目,直到弹出第k个项目。 (由于底行是排序的,因此用它为堆播种是一件小事。)

这种复杂性是O(k log n) - 其中&#39; n&#39;是列数。如果您处理行,该程序同样有效...所以如果有&#39; m&#39;行和&#39; n&#39;列,你可以选择两者中较小的一个!

NB:复杂性 O(k log k)...并且因为对于给定的A和B对,&#39; n&#39 ;是常数,O(k log n)实际上是O(k)!!

如果你想为不同的&#39;做许多探测,那么诀窍可能是不时地缓存进程的状态,以便将来&# 39; k可以通过从最近的检查点重新开始来完成。在限制中,可以运行合并完成并存储所有可能的值,用于O(1)查找!