Maximum Area Rectangle With Point Constraints II
HardPrompt
There are n points on an infinite plane. You are given two integer arrays xCoord and yCoord where (xCoord[i], yCoord[i]) represents the coordinates of the ith point.
Your task is to find the maximum area of a rectangle that:
- Can be formed using four of these points as its corners.
- Does not contain any other point inside or on its border.
- Has its edges parallel to the axes.
Return the maximum area that you can obtain or -1 if no such rectangle is possible.
Example 1:
Input: xCoord = [1,1,3,3], yCoord = [1,3,1,3]
Output: 4
Explanation:

We can make a rectangle with these 4 points as corners and there is no other point that lies inside or on the border. Hence, the maximum possible area would be 4.
Example 2:
Input: xCoord = [1,1,3,3,2], yCoord = [1,3,1,3,2]
Output: -1
Explanation:

There is only one rectangle possible is with points [1,1], [1,3], [3,1] and [3,3] but [2,2] will always lie inside it. Hence, returning -1.
Example 3:
Input: xCoord = [1,1,3,3,1,3], yCoord = [1,3,1,3,2,2]
Output: 2
Explanation:

The maximum area rectangle is formed by the points [1,3], [1,2], [3,2], [3,3], which has an area of 2. Additionally, the points [1,1], [1,2], [3,1], [3,2] also form a valid rectangle with the same area.
Constraints:
1 <= xCoord.length == yCoord.length <= 2 * 1050 <= xCoord[i], yCoord[i] <= 8 * 107- All the given points are unique.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach improves upon the brute-force method by using a sweep-line technique. It processes the points sorted by one coordinate (e.g., y-coordinate) and uses a hash map to keep track of potential rectangle edges seen so far. This avoids the O(N^3) complexity by intelligently constructing and verifying only the rectangles that have a chance of being empty.
Algorithm
-
- Establish the necessary and sufficient conditions for an empty rectangle: its four edges must be empty of other points. This implies that its corners are formed by points that are mutually adjacent in the point-defined grid.
-
- Pre-process the points into two maps:
xMap(mapping each x-coordinate to a sorted list of its y-coordinates) andyMap(mapping each y-coordinate to a sorted list of its x-coordinates).
- Pre-process the points into two maps:
-
- Initialize
maxArea = -1and a hash maplastYSeen. This map will store, for a pair of x-coordinates(x1, x2), the y-coordinate where they last appeared as an adjacent horizontal segment.
- Initialize
-
- Perform a horizontal sweep by iterating through a sorted list of unique y-coordinates.
-
- For each
y, get its sorted list of x-coordinatesxCoordsfromyMap.
- For each
-
- Iterate through adjacent pairs
(x1, x2)inxCoords. This pair forms an empty horizontal segment.
- Iterate through adjacent pairs
-
- Check if the pair
(x1, x2)exists as a key inlastYSeen. If it does, with a valueprevY, we have a candidate rectangle with empty top and bottom edges.
- Check if the pair
-
- Verify that the vertical edges are also empty. This is done by checking if
yis the immediate successor ofprevYin the sorted y-lists for bothx1andx2(fromxMap).
- Verify that the vertical edges are also empty. This is done by checking if
-
- If all adjacency conditions hold, calculate the area and update
maxArea.
- If all adjacency conditions hold, calculate the area and update
-
- Update
lastYSeenwith the current(x1, x2)pair and its y-coordinatey.
- Update
Walkthrough
The core idea is based on the insight that an empty rectangle must be bounded by adjacent points. For a rectangle with corners (x1, y1), (x2, y1), (x1, y2), (x2, y2) to be empty, its horizontal and vertical edges must not contain any other points.
We can implement this with a horizontal sweep-line algorithm. We first pre-process all points into two maps: one mapping x-coordinates to their sorted y-coordinates (xMap), and another mapping y-coordinates to their sorted x-coordinates (yMap).
We then sweep a line from bottom to top by iterating through the unique y-coordinates in sorted order. At each y level, we look at the points on that line. We consider every adjacent pair of points (x1, y) and (x2, y). This forms an empty horizontal segment. We use a hash map, lastYSeen, to remember the y-coordinate where we last saw the horizontal segment defined by (x1, x2).
If we find the pair (x1, x2) in our map, it means we have found a previous empty horizontal segment at prevY with the same x-coordinates. This gives us a rectangle with empty top and bottom edges. The final step is to verify that the vertical edges are also empty. We do this by using our xMap to check if y is the immediate successor of prevY in the sorted y-lists for both x1 and x2. If this holds, the rectangle is empty, and we update our maximum area.
import java.util.*; class Solution { public long maxAreaRectangle(int[] xCoord, int[] yCoord) { int n = xCoord.length; Map<Integer, List<Integer>> xMap = new HashMap<>(); Map<Integer, List<Integer>> yMap = new HashMap<>(); for (int i = 0; i < n; i++) { xMap.computeIfAbsent(xCoord[i], k -> new ArrayList<>()).add(yCoord[i]); yMap.computeIfAbsent(yCoord[i], k -> new ArrayList<>()).add(xCoord[i]); } for (List<Integer> list : xMap.values()) { Collections.sort(list); } for (List<Integer> list : yMap.values()) { Collections.sort(list); } List<Integer> sortedY = new ArrayList<>(yMap.keySet()); Collections.sort(sortedY); long maxArea = -1; Map<Long, Integer> lastYSeen = new HashMap<>(); for (int y : sortedY) { List<Integer> xCoords = yMap.get(y); for (int i = 0; i < xCoords.size() - 1; i++) { int x1 = xCoords.get(i); int x2 = xCoords.get(i + 1); long pairKey = pack(x1, x2); if (lastYSeen.containsKey(pairKey)) { int prevY = lastYSeen.get(pairKey); // Check vertical adjacency if (isVerticallyAdjacent(xMap, x1, prevY, y) && isVerticallyAdjacent(xMap, x2, prevY, y)) { long area = (long)(x2 - x1) * (y - prevY); if (maxArea == -1 || area > maxArea) { maxArea = area; } } } lastYSeen.put(pairKey, y); } } return maxArea; } private boolean isVerticallyAdjacent(Map<Integer, List<Integer>> xMap, int x, int y1, int y2) { List<Integer> yList = xMap.get(x); int idx = Collections.binarySearch(yList, y1); return idx >= 0 && idx + 1 < yList.size() && yList.get(idx + 1) == y2; } private long pack(int x1, int x2) { return ((long)x1 << 32) | x2; }}Complexity
Time
O(N log N), where N is the number of points. The preprocessing step of creating and sorting the lists in the maps takes `O(N log N)`. The sweep-line itself involves iterating through `O(N)` adjacent pairs in total, with each check involving binary searches (`O(log N)`), leading to a total time complexity of `O(N log N)`.
Space
O(N), where N is the number of points. This space is used for the `xMap`, `yMap`, and the `lastYSeen` map, all of which can store up to `O(N)` elements or pairs in total.
Trade-offs
Pros
Efficient
O(N log N)time complexity, which is suitable for the given constraints.Systematically finds all valid empty rectangles.
Cons
The implementation is more complex than the brute-force approach.
Requires careful management of multiple data structures and understanding of the geometric properties.
This is arguably the most elegant and efficient approach. It is based on a crucial insight into the geometric structure of empty rectangles. It establishes that for a rectangle to be empty, its corners must be formed by points that are mutually adjacent in the grid defined by the input points themselves.
Algorithm
-
- Discover the key property: A rectangle is empty if and only if its four corners
(x1, y1), (x1, y2), (x2, y1), (x2, y2)are all input points, and they are mutually adjacent in the grid formed by the points.
- Discover the key property: A rectangle is empty if and only if its four corners
-
- Pre-process the points into
xMap(x -> sorted list of y's),yMap(y -> sorted list of x's), and aHashSetof all points forO(1)existence checks.
- Pre-process the points into
-
- Initialize
maxArea = -1.
- Initialize
-
- Iterate through each point
(x1, y1)in the input, treating it as a potential bottom-left corner.
- Iterate through each point
-
- Using
xMap, find the y-coordinatey2that is immediately aftery1for the x-coordinatex1. This gives the 'up' neighbor. If none exists, continue.
- Using
-
- Using
yMap, find the x-coordinatex2that is immediately afterx1for the y-coordinatey1. This gives the 'right' neighbor. If none exists, continue.
- Using
-
- Now we have three corners of a potential empty rectangle:
(x1, y1), (x1, y2), (x2, y1). The fourth corner must be(x2, y2).
- Now we have three corners of a potential empty rectangle:
-
- Check if the point
(x2, y2)exists in theHashSet.
- Check if the point
-
- If it exists, verify the remaining two adjacency conditions: Is
y2adjacent toy1forx2? Isx2adjacent tox1fory2? These checks are done via binary search in the pre-processed maps.
- If it exists, verify the remaining two adjacency conditions: Is
-
- If all four adjacency conditions hold, the rectangle is guaranteed to be empty. Calculate its area and update
maxArea.
- If all four adjacency conditions hold, the rectangle is guaranteed to be empty. Calculate its area and update
Walkthrough
The fundamental property of an empty rectangle defined by (x1, y1), (x2, y1), (x1, y2), (x2, y2) is that there are no other input points on its boundary or in its interior. This implies a set of strong adjacency conditions that are both necessary and sufficient:
y1andy2must be adjacent y-coordinates among all points with x-coordinatex1.y1andy2must also be adjacent for x-coordinatex2.- Similarly,
x1andx2must be adjacent x-coordinates for y-coordinatey1. x1andx2must also be adjacent for y-coordinatey2.
This approach leverages this property by iterating through each point and treating it as a potential bottom-left corner. For each point (x1, y1), it finds its "up" neighbor (x1, y2) and "right" neighbor (x2, y1) using pre-processed maps. These maps store sorted lists of coordinates, allowing for efficient neighbor lookups using binary search. These three points define the potential fourth corner (x2, y2). If this fourth corner exists, the algorithm then simply verifies the remaining two adjacency conditions to confirm the rectangle is empty. Because we check every point as a potential corner, we are guaranteed to find the maximum area empty rectangle.
import java.util.*; class Solution { public long maxAreaRectangle(int[] xCoord, int[] yCoord) { int n = xCoord.length; Map<Integer, List<Integer>> xMap = new HashMap<>(); Map<Integer, List<Integer>> yMap = new HashMap<>(); Set<Long> pointSet = new HashSet<>(); for (int i = 0; i < n; i++) { xMap.computeIfAbsent(xCoord[i], k -> new ArrayList<>()).add(yCoord[i]); yMap.computeIfAbsent(yCoord[i], k -> new ArrayList<>()).add(xCoord[i]); pointSet.add(pack(xCoord[i], yCoord[i])); } for (List<Integer> list : xMap.values()) Collections.sort(list); for (List<Integer> list : yMap.values()) Collections.sort(list); long maxArea = -1; for (int i = 0; i < n; i++) { int x1 = xCoord[i]; int y1 = yCoord[i]; List<Integer> yList = xMap.get(x1); List<Integer> xList = yMap.get(y1); int y1_idx = Collections.binarySearch(yList, y1); int x1_idx = Collections.binarySearch(xList, x1); // Consider points "up" and "right" from (x1, y1) if (y1_idx + 1 < yList.size() && x1_idx + 1 < xList.size()) { int y2 = yList.get(y1_idx + 1); int x2 = xList.get(x1_idx + 1); // Check if the 4th corner (x2, y2) exists if (pointSet.contains(pack(x2, y2))) { // Verify the other two adjacency conditions List<Integer> yListForX2 = xMap.get(x2); int y1_idx_in_x2 = Collections.binarySearch(yListForX2, y1); List<Integer> xListForY2 = yMap.get(y2); int x1_idx_in_y2 = Collections.binarySearch(xListForY2, x1); if (y1_idx_in_x2 >= 0 && y1_idx_in_x2 + 1 < yListForX2.size() && yListForX2.get(y1_idx_in_x2 + 1) == y2 && x1_idx_in_y2 >= 0 && x1_idx_in_y2 + 1 < xListForY2.size() && xListForY2.get(x1_idx_in_y2 + 1) == x2) { long area = (long)(x2 - x1) * (y2 - y1); if (maxArea == -1 || area > maxArea) { maxArea = area; } } } } } return maxArea; } private long pack(long x, long y) { return (x << 32) | y; }}Complexity
Time
O(N log N), where N is the number of points. Preprocessing takes `O(N log N)`. The main loop iterates N times, and inside the loop, we perform a constant number of binary searches (`O(log N)`) on lists of size at most N. This results in a total time of `O(N log N)`.
Space
O(N), where N is the number of points. Space is needed for the `xMap`, `yMap`, and the `pointSet`.
Trade-offs
Pros
Highly efficient with
O(N log N)time complexity.Conceptually clean and direct once the geometric property is understood.
Guaranteed to be correct and avoids complex state management seen in some sweep-line variants.
Cons
The logic relies on a non-trivial geometric insight which might not be immediately obvious.
Requires significant preprocessing to build multiple data structures (
xMap,yMap,pointSet).
This approach iterates through all possible pairs of points, considering them as potential diagonals of a rectangle. For each potential rectangle, it first verifies that all four corner points exist in the input set. Then, it checks the emptiness condition by performing a linear scan through all other points to ensure none lie inside or on the rectangle's boundary.
Algorithm
-
- Store all
npoints in aHashSetforO(1)average time lookups. A point can be represented as alongby packing its x and y coordinates.
- Store all
-
- Initialize
maxArea = -1.
- Initialize
-
- Iterate through each point
p1fromi = 0ton-1.
- Iterate through each point
-
- Iterate through each other point
p2fromj = i + 1ton-1.
- Iterate through each other point
-
- Let
p1 = (x1, y1)andp2 = (x2, y2). Ifx1 == x2ory1 == y2, these points cannot form a non-degenerate rectangle's diagonal, so we skip them.
- Let
-
- The other two corners would be
p3 = (x1, y2)andp4 = (x2, y1). Check if pointsp3andp4exist in theHashSet.
- The other two corners would be
-
- If they exist, we have found a rectangle. Assume it's empty (
isEmpty = true).
- If they exist, we have found a rectangle. Assume it's empty (
-
- To verify emptiness, iterate through all
npointsp_check = (px, py).
- To verify emptiness, iterate through all
-
- If
p_checkis not one of the four corners and it lies within the rectangle's bounds (min(x1,x2) <= px <= max(x1,x2)andmin(y1,y2) <= py <= max(y1,y2)), setisEmpty = falseand break the check.
- If
-
- If
isEmptyremains true, calculate the areaabs((long)(x2 - x1) * (y2 - y1))and updatemaxArea.
- If
-
- After checking all pairs, return
maxArea.
- After checking all pairs, return
Walkthrough
First, we store all points in a HashSet to allow for quick, O(1) average time lookups. A point (x, y) can be uniquely represented as a long by packing its coordinates, for instance, (long)x << 32 | y.
Then, we iterate through every unique pair of points p1(x1, y1) and p2(x2, y2). If x1 == x2 or y1 == y2, these points lie on the same horizontal or vertical line and cannot form a non-degenerate rectangle's diagonal, so we skip them. Otherwise, we form the other two potential corners p3(x1, y2) and p4(x2, y1).
We check if p3 and p4 exist in our HashSet. If they do, we have found the four corners of an axis-aligned rectangle. The crucial and most expensive step is to verify that this rectangle is "empty". We do this by iterating through all n points in the input. For each point, we check if it lies inside or on the boundary of our rectangle, making sure it's not one of the four corner points. If no such other points are found, the rectangle is valid. We calculate its area and update our maximum area found so far. This process is repeated for all O(N^2) pairs of points.
import java.util.HashSet;import java.util.Set; class Solution { public long maxAreaRectangle(int[] xCoord, int[] yCoord) { int n = xCoord.length; long[][] points = new long[n][2]; Set<Long> pointSet = new HashSet<>(); for (int i = 0; i < n; i++) { points[i][0] = xCoord[i]; points[i][1] = yCoord[i]; pointSet.add(pack(xCoord[i], yCoord[i])); } long maxArea = -1; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { long x1 = points[i][0]; long y1 = points[i][1]; long x2 = points[j][0]; long y2 = points[j][1]; if (x1 == x2 || y1 == y2) { continue; } if (pointSet.contains(pack(x1, y2)) && pointSet.contains(pack(x2, y1))) { // Found a rectangle, now check if it's empty boolean isEmpty = true; for (int k = 0; k < n; k++) { long px = points[k][0]; long py = points[k][1]; // Check if the point is one of the 4 corners if ((px == x1 && py == y1) || (px == x1 && py == y2) || (px == x2 && py == y1) || (px == x2 && py == y2)) { continue; } // Check if the point is inside or on the boundary if (px >= Math.min(x1, x2) && px <= Math.max(x1, x2) && py >= Math.min(y1, y2) && py <= Math.max(y1, y2)) { isEmpty = false; break; } } if (isEmpty) { long area = Math.abs((x2 - x1) * (y2 - y1)); if (maxArea == -1 || area > maxArea) { maxArea = area; } } } } } return maxArea; } private long pack(long x, long y) { return (x << 32) | y; }}Complexity
Time
O(N^3), where N is the number of points. We iterate through `O(N^2)` pairs of points. For each potential rectangle found, we perform an emptiness check which takes `O(N)` time.
Space
O(N), where N is the number of points. This is required to store the points in a `HashSet` for efficient lookups.
Trade-offs
Pros
Relatively simple to understand and implement the logic.
Correctly solves the problem for small input sizes.
Cons
The
O(N^3)time complexity is highly inefficient and will result in a 'Time Limit Exceeded' error for the given constraints.
Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.