想象一下,你有两个麻袋(A
和B
)分别有N
和M
个球。每个球都有一个已知的数值(利润)。你被要求提取(有替换)这对球的最大总利润(由所选球的乘法给出)。
最佳提取是显而易见的:从A
以及B
中选择最有价值的球。
当您被要求提供第二或第k个最佳选择时,问题就出现了。按照上一种方法,您应该从A
和B
中选择最有价值的球,而不重复选择。
这可以笨拙地解决,计算每个可能的选择的值,排序和排序(例如在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))
,我打赌有更好的解决方案。
我根据表格找到了一个解决方案,其中A
和B
元素从较高值到较低值排序,并且这些值中的每一个都关联了一个索引,该索引表示要从另一列测试的下一个值。最初这个表看起来像:
A
中的第一个元素是25
,必须针对index 2 select from b = 0
对其进行测试(20
),因此25*20=500
是第一个最佳选择,在增加要检查的索引之后,表格将更改为:
使用这些索引,我们可以迅速获得最佳候选人选择:
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)
我的问题是:
答案 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)查找!