Enumerate grid points on 2D plane with descending order of (x * y)

前端 未结 8 1275
轻奢々
轻奢々 2021-02-09 11:55

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

相关标签:
8条回答
  • 2021-02-09 12:20

    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.

    0 讨论(0)
  • 2021-02-09 12:24

    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:

    1. Only descend to 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.
    2. Only consider the columns that could possibly contain the maximum. In the example I gave, there's no reason to include 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
    
    0 讨论(0)
提交回复
热议问题