Maximum possible number of rectangles that can be crossed with one straight line

I found this problem issue which reads as follows:

Suppose there are n rectangles on the XY plane. Write a program to calculate the maximum possible number of rectangles that can be crossed with a single straight line drawn on this plane.

see image for an example

I've been brainstorming for a long time but haven't found a solution. We may be using dynamic programming steps at some stage but can't figure out how to get started.

+31


source to share


6 answers


Here is a sketch of an O (n ^ 2 log n) solution.

First, preliminary data were shared with other responses. When we have a line going through some rectangles, we can translate it to either of the two sides until it goes through the corner of some rectangle. After that, we fix this angle as the center of rotation and rotate the line to either side until it passes through the other corner. Throughout the entire process, all the intersection points between our line and the sides of the rectangle remained on these sides, so the number of intersections remained the same, as did the number of rectangles intersected by the line. As a result, we can only consider lines passing through two rectangles, which are bounded by O (n ^ 2) and are a nice improvement over an infinite space of arbitrary lines.

So how do we effectively validate all these lines? First, let's have an outer loop that captures one point A and then considers all lines through A. There is O (n) choice of A.

Now we fix one point A and want to consider all lines AB passing through all other angles B. To do this, first sort all other angles B according to the polar angle AB, or, in other words, the angle between the Ox axis and the vector AB. Angles are measured from -PI to + PI or 0 to 2 PI or otherwise, the point at which we cut the circle to sort the corners can be arbitrary. The sort is done in O (n log n).

We now have points B 1, B 2, ..., B k, sorted by the polar angle around point A (their k number is something like 4n-4, all corners of all rectangles except those where point A is an angle). First look at line AB 1and count the number of rectangles intersected by this line in O (n). After that, consider turning AB 1 on AB 2, then AB 2 on AB 3, down to AB <sub> ksub>. The events that occur during rotation are as follows:



  • When we turn to AB iand B i - this is the first corner of some rectangle in our order, the number of crossed rectangles increases by 1 as soon as the rotating line hits B i...

  • When we turn to AB jand B j is the last corner of some rectangle in our order, the number of crossed rectangles decreases by 1 as soon as the line rotates past B j...

Which corners are the first and last can be set with some O (n) preprocessing after sorting, but before considering ordered events.

In short, we can rotate to the next such event and update the number of rectangles crossed in O (1). And there are k = O (n) events in total. It remains to be done so as to track the global maximum of this value in the entire algorithm. The answer is precisely this maximum.

The whole algorithm runs in O (n * (n log n + n + n)), which is O (n ^ 2 log n), just as advertised.

+8


source


(Edit my earlier answer, which dealt with plane rotation.)

Here's a sketch of an algorithm O(n^2)

that combines Gass's idea with Evgeny Kluyev, referencing two-line devices as angular sorted sequences.

We start with a doubly linked edge list or similar structure, allowing us to split the edge in O(1)

time and the method for moving faces that we create when we fill the 2D plane. For simplicity, let's only use three of the twelve corners on the rectangles below:

9|     (5,9)___(7,9)
8|         |   |
7|    (4,6)|   |
6|    ___C |   |
5|   |   | |   |
4|   |___| |   |
3|  ___    |___|(7,3)
2| |   |  B (5,3)
1|A|___|(1,1)
 |_ _ _ _ _ _ _ _
   1 2 3 4 5 6 7

      

Insert three points (corners) into the double plane according to the following transformation:

point p => line p* as a*p_x - p_y
line l as ax + b => point l* as (a, -b)

      

Let's introduce the order points A, B, C

. Let's introduce first A => y = x - 1

. Since there is only one edge at the moment, we insert B => y = 5x - 3

which creates a vertex (1/2, -1/2)

and splits our edge. (One elegant aspect of this solution is that each vertex (point) in a double plane is actually a double point of a line through the corners of the rectangles. Observe 1 = 1/2*1 + 1/2

and 3 = 1/2*5 + 1/2

, points (1,1)

and (5,3)

.)

Entering the last point C => y = 4x - 6

,, now we are looking for the leftmost face (maybe an incomplete face) where it will intersect. This search for O(n)

time, as we must try every face. We will find and create the top (-3, -18)

by splitting the bottom edge 5x - 3

and crossing the edges to split the right half x - 1

at the top (5/3, 2/3)

. Each insertion has a O(n)

time since we must first find the leftmost face, then cross each face to separate the edges and mark the vertices (intersection points for the line).

In the dual plane we have:



enter image description here

After building the row layout, we begin our iteration at our three approximate points (rectangular corners). Part of the magic in restoring an angular ordered sequence in relation to a single point is to split the corners (each corresponding to an ordered intersection of lines in a double plane) into those corresponding to a point on the right (with a larger x coordinate) and left, and concatenate the two sequences so that get an ordered sequence from -90 to -270 degrees. (The points on the right are converted to lines with positive slopes with respect to the fixed point, and on the left with negative slopes. Turn the selector / screen clockwise until the line for (C*) 4x - 6

is horizontal and you will see that it B*

now has a positive slope andA*

negative.)

Why does it work? If the point is p

converted to a straight line p*

in the double plane in the original plane , then the intersection of this double line from left to right corresponds to the rotation of the line around p

in the original plane, which also passes through p

. The double line marks all slopes of this rotating line with the x-coordinate from negative infinity (vertical) to zero (horizontal) to infinity (vertical again).

(Let's sum the rectangle-count-logic, update the count_array for the current rectangle when iterating through the angular sequence: if it's 1, increase the current intersection count, if it's 4 and the row is not directly at the corner, set it to 0 and decrease the current intersection count. )

Pick A, lookup A*
=> x - 1.

Obtain the concatenated sequence by traversing the edges in O(n)
=> [(B*) 5x - 3, (C*) 4x - 6] ++ [No points left of A]

Initialise an empty counter array, count_array of length n-1

Initialise a pointer, ptr, to track rectangle corners passed in
the opposite direction of the current vector.

Iterate:
  vertex (1/2, -1/2)
  => line y = 1/2x + 1/2 (AB)

  perform rectangle-count-logic

  if the slope is positive (1/2 is positive):
    while the point at ptr is higher than the line:
      perform rectangle-count-logic

  else if the slope is negative:
    while the point at ptr is lower than the line:
      perform rectangle-count-logic

  => ptr passes through the rest of the points up to the corner
     across from C, so intersection count is unchanged

  vertex (5/3, 2/3)
  => line y = 5/3x - 2/3 (AC)

      

We can see what (5,9)

is above the line through AC (y = 5/3x - 2/3)

, which means that at that moment we would count the intersection with the rightmost rectangle and still not reset the counter for it, there are only 3 rectangles for this line.

We can also see in the double plane plot, other angular sequences:

for point B => B* => 5x - 3: [No points right of B] ++ [(C*) 4x - 6, (A*) x - 1]

for point C => C* => 4x - 6: [(B*) 5x - 3] ++ [(A*) x - 1]
(note that we start at -90 deg up to -270 deg)

      

+4


source


Decision

In the space of all lines in the graph, the lines along the corner correspond exactly to where the number or intersections are about to decrease. In other words, each of them forms a local maximum.

And for every row that goes through at least one corner, there is a corresponding row going through two corners with the same number of intersections.

The conclusion is that we only need to check the lines formed by the two rectangular corners, since they form a set that fully represents the local maxima of our problem. Of these, we choose the one that has the greatest intersection.

Complexity of time

This solution must first reconstruct all the lines that go through the two corners. The number of such lines is O (n ^ 2).

Then we need to count the number of intersections between this line and the rectangle. This can obviously be done in O (n) by comparing to each rectangle.

Maybe a more efficient way to proceed, but we know that this algorithm is then at most O (n ^ 3).

Python3 implementation

Here is a Python implementation of this algorithm. I was focusing on readability rather than efficiency, but it does exactly what is stated above.

def get_best_line(rectangles):
    """
    Given a set of rectangles, return a line which intersects the most rectangles.
    """

    # Recover all corners from all rectangles
    corners = set()
    for rectangle in rectangles:
        corners |= set(rectangle.corners)

    corners = list(corners)

    # Recover all lines passing by two corners
    lines = get_all_lines(corners)

    # Return the one which has the highest number of intersections with rectangles
    return max(
        ((line, count_intersections(rectangles, line)) for line in lines),
        key=lambda x: x[1])

      

This implementation uses the following helpers.

def get_all_lines(points):
    """
    Return a generator providing all lines generated
    by a combination of two points out of 'points'
    """
    for i in range(len(points)):
        for j in range(i, len(points)):
            yield Line(points[i], points[j])

def count_intersections(rectangles, line):
    """
    Return the number of intersections with rectangles
    """
    count = 0

    for rectangle in rectangles:
        if line in rectangle:
           count += 1

    return count

      

And here is a class definition that serves as a data structure for rectangles and strings.

import itertools
from decimal import Decimal

class Rectangle:
    def __init__(self, x_range, y_range):
        """
        a rectangle is defined as a range in x and a range in y.
        By example, the rectangle (0, 0), (0, 1), (1, 0), (1, 1) is given by
        Rectangle((0, 1), (0, 1))
        """
        self.x_range = sorted(x_range)
        self.y_range = sorted(y_range)

    def __contains__(self, line):
        """
        Return whether 'line' intersects the rectangle.
        To do so we check if the line intersects one of the diagonals of the rectangle
        """
        c1, c2, c3, c4 = self.corners

        x1 = line.intersect(Line(c1, c4))
        x2 = line.intersect(Line(c2, c3))

        if x1 is True or x2 is True \
                or x1 is not None and self.x_range[0] <= x1 <= self.x_range[1] \
                or x2 is not None and self.x_range[0] <= x2 <= self.x_range[1]:

            return True

        else:
            return False

    @property
    def corners(self):
        """Return the corners of the rectangle sorted in dictionary order"""
        return sorted(itertools.product(self.x_range, self.y_range))


class Line:
    def __init__(self, point1, point2):
        """A line is defined by two points in the graph"""
        x1, y1 = Decimal(point1[0]), Decimal(point1[1])
        x2, y2 = Decimal(point2[0]), Decimal(point2[1])
        self.point1 = (x1, y1)
        self.point2 = (x2, y2)

    def __str__(self):
        """Allows to print the equation of the line"""
        if self.slope == float('inf'):
            return "y = {}".format(self.point1[0])

        else:
            return "y = {} * x + {}".format(round(self.slope, 2), round(self.origin, 2))

    @property
    def slope(self):
        """Return the slope of the line, returning inf if it is a vertical line"""
        x1, y1, x2, y2 = *self.point1, *self.point2

        return (y2 - y1) / (x2 - x1) if x1 != x2 else float('inf')

    @property
    def origin(self):
        """Return the origin of the line, returning None if it is a vertical line"""
        x, y = self.point1

        return y - x * self.slope if self.slope != float('inf') else None

    def intersect(self, other):
        """
        Checks if two lines intersect.
        Case where they intersect: return the x coordinate of the intersection
        Case where they do not intersect: return None
        Case where they are superposed: return True
        """

        if self.slope == other.slope:

            if self.origin != other.origin:
                return None

            else:
                return True

        elif self.slope == float('inf'):
            return self.point1[0]

        elif other.slope == float('inf'):
            return other.point1[0]

        elif self.slope == 0:
            return other.slope * self.origin + other.origin

        elif other.slope == 0:
            return self.slope * other.origin + self.origin

        else:
            return (other.origin - self.origin) / (self.slope - other.slope)

      

Example

Here is a working example of the above code.

rectangles = [
    Rectangle([0.5, 1], [0, 1]),
    Rectangle([0, 1], [1, 2]),
    Rectangle([0, 1], [2, 3]),
    Rectangle([2, 4], [2, 3]),
]

# Which represents the following rectangles (not quite to scale)
#
#  *
#  *   
#
# **     **
# **     **
#
# **
# **

      

We can clearly see that the optimal solution is to find a line that goes over three rectangles, and that is indeed what it outputs.

print('{} with {} intersections'.format(*get_best_line(rectangles)))
# prints: y = 0.50 * x + -5.00 with 3 intersections

      

+4


source


How about the following algorithm:

RES = 0 // maximum number of intersections
CORNERS[] // all rectangles corners listed as (x, y) points

for A in CORNERS
    for B in CORNERS // optimization: starting from corner next to A
        RES = max(RES, CountIntersectionsWithLine(A.x, A.y, B.x, B.y))

return RES

      

In other words, start drawing lines from each corner of the rectangle towards each other in a rectangle and find the maximum number of intersections. As suggested by @weston, we can avoid evaluating the same row twice by running the inner loop from the corner next to A

.

+3


source


If you view the rotating line at an angle Θ, and if you project all rectangles onto that line, you get N line segments. The maximum number of rectangles perpendicular to this line can be easily obtained by sorting the endpoints by increasing the abscissa and keeping a count of the intervals that occur from left to right (watch whether the endpoint is the beginning or the end). This is displayed in green.

Now the two rectangles are intersected by all lines at an angle between the two interior tangents (example in red), so that all considered "event" angles must be considered (i.e. all angles for which the counter change can be observed) - these N (N- 1) corners.

Then the brute force transformation circuit

  • for all limiting angles (O (N²) of them),

    • project rectangles on a rotating line (O (N) operations),

    • count the overlaps and store the largest (O (N Log N) for sorting, then O (N) for counting).

This applies to general operations O (N³Log N).

enter image description here

Assuming that the sorts do not need to be reworked completely for each corner, if we can do them in stages, we can hope to reduce the complexity to O (N³). This needs to be checked.


Note:

Solutions that constrain straight lines through the corner of one rectangle are incorrect. If you draw wedges from the four corners of a rectangle to the full length of the other, there will be an empty space that can contain a whole rectangle that will not touch, although there is a line between them.

enter image description here

+2


source


We may have a dynamic programming method O(n^2 (log n + m))

, adapting Andrey Berestovsky's idea to iterate over corners, to insert the ratio of the current angle over all other rectangles into the interval tree for each of our 4n

iteration loops.

A new tree will be created in the corner we are trying. For each rectangle, we'll iterate over the four corners along each of the other rectangles. What we are going to insert will be the arc corners that form the closest corners of the paired rectangle relative to the current fixed angle.

In the example below, for a fixed bottom corner of a rectangle, R

when inserting an entry for the middle rectangle, we insert corners that represent an arc from p2

to p1

in relation to R

(about (37 deg, 58 deg)

). Then, when we check the tall rectangle with respect to R

, we will insert a spacing of corners that denotes an arc from p4

to p3

in relation to R

(about (50 deg, 62 deg)

).

When we insert the next arc record, we will check it against all intersecting intervals and keep a record of most intersections.

enter image description here

(Note that since any arc on a 360-degree circle has a counterpart rotated 180 degrees for our target, we may need to do arbitrary clipping (any alternative ideas would be welcome). For example, this means an arc from 45 degrees to 315 degrees would divide by two: [0, 45] and [135, 180]. Any unexpanded arc could only intersect with one or the other, but in any case, we may need an additional hash to make sure that the rectangles are not considered double.)

+1


source







All Articles