Given N > 0
and M > 0
, I want to enumerate all (x, y) pairs such that 1 <= x <= N and 1 <= y <= M in descending order of (x * y).
An
Here's an O(K logK) solution, where K is the number of terms you want to generate.
Edit: Q keeps only one copy of each element; an insert fails if the element is already present.
priority_queue Q
Q.insert( (N*M, (N,M)) ) // priority, (x,y)
repeat K times:
(v, (x,y)) = Q.extract_max()
print(v, (x,y))
Q.insert( (x-1)*y, (x-1,y) )
Q.insert( x*(y-1), (x,y-1) )
This works because before visiting (x,y) you must visit either (x+1,y) or (x,y+1). The complexity is O(KlogK) since Q at most 2K elements are pushed into it.
Here's an algorithm for you. I'll try to give you an English description.
In the rectangle we're working with, we can always assume that a point P(x, y)
has a greater "area" than the point below it P(x, y-1)
. So when looking for points of maximum area, we only need to compare the topmost untaken point in each column (i.e. for each possible x
). For example, when considering the pristine 3 x 5
grid
5 a b c
4 d e f
3 g h i
2 j k l
1 m n o
1 2 3
we really only need to compare a
, b
, and c
. All the other points are guaranteed to have less area than at least one of those. So build a max heap that only contains the topmost point in each column. When you pop a point out of the heap, push in the point that's directly below it (if that point exists). Repeat until the heap is empty. This gives you the following algorithm (tested, but it's in Ruby):
def enumerate(n, m)
heap = MaxHeap.new
n.times {|i| heap.push(Point.new(i+1, m))}
until(heap.empty?)
max = heap.pop
puts "#{max} : #{max.area}"
if(max.y > 1)
max.y -= 1
heap.push(max)
end
end
end
This gives you a running time of O((2k + N) log N)
. Heap operations cost log N
; we do N
of them when building the initial heap, and then 2k
when we pull out the k
points of maximum area (2 assuming each pop is followed by the push of the point below it).
It has the additional advantage of not having to build all your points, unlike the original proposal of building the entire set and then sorting. You only build as many points as necessary to keep your heap accurate.
And finally: Improvements can be made! I haven't done these, but you could get even better performance with the following tweaks:
y = x
in each column instead of y = 1
. To generate the points that you're no longer checking, use the fact that the area of P(x, y)
is equal to the area of P(y, x)
. Note: If you use this method, you'll need two versions of the algorithm. Columns work when M >= N
, but if M < N
you'll need to do this by rows instead.a
in the heap from the beginning, since it's guaranteed to be less than b
. So you only need to add columns to the heap when the top points of their neighbour columns get popped.And that turned into a small essay... Anyways - hope it helps!
Edit: The full algorithm, incorporating both improvements I mentioned above (but still in Ruby, 'cause I'm lazy). Note that there's no need for any extra structures to avoid inserting duplicates - unless it's a "top" point, each point will only insert another point in it's row/column when taken.
def enumerate(n, m, k)
heap = MaxHeap.new
heap.push(Point.new(n, m))
result = []
loop do
max = heap.pop
result << max
return result if result.length == k
result << Point.new(max.y, max.x) if max.x <= m && max.y <= n && max.x != max.y
return result if result.length == k
if(m < n) # One point per row
heap.push(Point.new(max.x, max.y - 1)) if max.x == n && max.y > 1
heap.push(Point.new(max.x - 1, max.y)) if max.x > max.y
else # One point per column
heap.push(Point.new(max.x - 1, max.y)) if max.y == m && max.x > 1
heap.push(Point.new(max.x, max.y - 1)) if max.y > max.x
end
end
end