3025. Find the Number of Ways to Place People I
Problem Description
You are given a 2D array points
of size n x 2
representing integer coordinates of some points on a 2D plane, where points[i] = [xi, yi]
.
The task is to count the number of valid pairs of points (A, B)
that satisfy two conditions:
-
Point
A
is on the upper left side of pointB
. This means:- The x-coordinate of
A
is less than or equal to the x-coordinate ofB
(A.x <= B.x
) - The y-coordinate of
A
is greater than or equal to the y-coordinate ofB
(A.y >= B.y
)
- The x-coordinate of
-
There are no other points inside or on the boundary of the rectangle formed by points
A
andB
. The rectangle hasA
as the upper-left corner andB
as the lower-right corner.
For example, if A = (1, 3)
and B = (4, 1)
, they form a rectangle with corners at (1, 3)
, (4, 3)
, (1, 1)
, and (4, 1)
. For this pair to be valid, no other points from the array should lie within this rectangular region or on its edges.
Return the total count of such valid pairs.
Intuition
To find valid pairs efficiently, we need to avoid checking every possible pair against all other points, which would be too slow. The key insight is to process points in a specific order that allows us to eliminate invalid pairs quickly.
If we sort the points by x-coordinate (ascending) and then by y-coordinate (descending for same x), we create an ordering where potential point A
candidates come before their corresponding point B
candidates. This sorting strategy (x, -y)
ensures that when we fix a point as A
, all points that could potentially be B
(having x >= A.x
) come after it in the sorted array.
For each point acting as A
, we iterate through subsequent points as potential B
candidates. Since B
must be to the lower-right of A
, we need B.y <= A.y
. But here's the clever part: we don't need to check all other points for the "no points in rectangle" condition individually.
Instead, we maintain a max_y
variable that tracks the highest y-coordinate we've seen so far among valid B
points. When we encounter a new potential B
, if its y-coordinate is higher than max_y
but still not exceeding A.y
, then we know there are no interfering points in the rectangle formed by A
and this new B
. This is because:
- Any point with a higher y-coordinate than
B.y
would need to be checked, but ifB.y > max_y
, we haven't seen any such interfering point yet - Points with lower y-coordinates than
max_y
don't matter because they would be outside or below the rectangle
By updating max_y
each time we find a valid pair, we effectively track a "barrier" that helps us quickly determine if future points form valid pairs with the current A
.
Solution Approach
The implementation follows a systematic approach using sorting and efficient pair validation:
Step 1: Sort the points
points.sort(key=lambda x: (x[0], -x[1]))
We sort points by x-coordinate in ascending order, and for points with the same x-coordinate, by y-coordinate in descending order. This ordering ensures that when we fix a point as A
, all potential B
points (with x >= A.x
) appear later in the sorted array.
Step 2: Iterate through each point as potential point A
for i, (_, y1) in enumerate(points):
We iterate through each point in the sorted array, treating it as the upper-left point A
. We only need the y-coordinate (y1
) since the x-coordinate ordering is already handled by the sort.
Step 3: Track the maximum y-coordinate barrier
max_y = -inf
For each point A
, we initialize max_y
to negative infinity. This variable tracks the highest y-coordinate among all valid B
points we've found so far for the current A
.
Step 4: Check subsequent points as potential point B
for _, y2 in points[i + 1 :]: if max_y < y2 <= y1: max_y = y2 ans += 1
For each subsequent point in the array (guaranteed to have x >= A.x
due to sorting), we check if it can form a valid pair with A
:
y2 <= y1
: Ensures pointB
is not above pointA
max_y < y2
: Ensures no previously seen point would be inside the rectangle
If both conditions are met, we've found a valid pair. We update max_y
to y2
and increment our answer counter.
The key efficiency comes from the max_y
tracking mechanism. Once a point with y-coordinate y2
becomes a valid B
, any future point with y-coordinate less than or equal to y2
cannot form a valid pair with the current A
because the point at y2
would be inside their rectangle. This eliminates the need to check all other points for each potential pair.
Time Complexity: O(n^2)
where n is the number of points - we have nested loops but each inner operation is O(1).
Space Complexity: O(1)
excluding the space used for sorting.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with a concrete example to understand how the algorithm works.
Given points: [[1,1], [2,2], [3,3]]
Step 1: Sort the points
After sorting by (x, -y)
: [[1,1], [2,2], [3,3]]
(In this case, the order remains the same since x-coordinates are all different and already in ascending order)
Step 2: Process each point as potential point A
Iteration 1: A = [1,1] (index 0)
- Initialize
max_y = -inf
- Check subsequent points as B:
- B = [2,2] (index 1):
- Is
max_y < 2 <= 1
? No, because2 > 1
(B.y must be ≤ A.y) - Not a valid pair
- Is
- B = [3,3] (index 2):
- Is
max_y < 3 <= 1
? No, because3 > 1
- Not a valid pair
- Is
- B = [2,2] (index 1):
- Valid pairs found: 0
Iteration 2: A = [2,2] (index 1)
- Initialize
max_y = -inf
- Check subsequent points as B:
- B = [3,3] (index 2):
- Is
max_y < 3 <= 2
? No, because3 > 2
- Not a valid pair
- Is
- B = [3,3] (index 2):
- Valid pairs found: 0
Iteration 3: A = [3,3] (index 2)
- No subsequent points to check
- Valid pairs found: 0
Total valid pairs: 0
Let's try another example with more interesting geometry:
Given points: [[1,3], [2,1], [3,2], [4,0]]
Step 1: Sort the points
After sorting by (x, -y)
: [[1,3], [2,1], [3,2], [4,0]]
Step 2: Process each point as potential point A
Iteration 1: A = [1,3] (index 0)
- Initialize
max_y = -inf
- Check subsequent points as B:
- B = [2,1]: Is
-inf < 1 <= 3
? Yes!- Valid pair: ([1,3], [2,1])
- Update
max_y = 1
, count = 1
- B = [3,2]: Is
1 < 2 <= 3
? Yes!- Valid pair: ([1,3], [3,2])
- Update
max_y = 2
, count = 2
- B = [4,0]: Is
2 < 0 <= 3
? No, because0 < 2
- Not valid (point [3,2] would be inside the rectangle)
- B = [2,1]: Is
Iteration 2: A = [2,1] (index 1)
- Initialize
max_y = -inf
- Check subsequent points as B:
- B = [3,2]: Is
-inf < 2 <= 1
? No, because2 > 1
- B = [4,0]: Is
-inf < 0 <= 1
? Yes!- Valid pair: ([2,1], [4,0])
- Update
max_y = 0
, count = 3
- B = [3,2]: Is
Iteration 3: A = [3,2] (index 2)
- Initialize
max_y = -inf
- Check subsequent points as B:
- B = [4,0]: Is
-inf < 0 <= 2
? Yes!- Valid pair: ([3,2], [4,0])
- Update
max_y = 0
, count = 4
- B = [4,0]: Is
Total valid pairs: 4
The key insight illustrated here is how max_y
acts as a barrier. For example, when A = [1,3] and we've already paired it with B = [3,2], the max_y = 2
prevents [4,0] from being a valid pair because the point [3,2] would lie inside the rectangle formed by [1,3] and [4,0].
Solution Implementation
1from typing import List
2from math import inf
3
4class Solution:
5 def numberOfPairs(self, points: List[List[int]]) -> int:
6 # Sort points by x-coordinate (ascending), then by y-coordinate (descending)
7 # This ensures we process points from left to right, and top to bottom for same x
8 points.sort(key=lambda point: (point[0], -point[1]))
9
10 pair_count = 0
11
12 # For each point as the first point of a potential pair
13 for i, (x1, y1) in enumerate(points):
14 # Track the maximum y-coordinate seen so far for valid pairs
15 # Initialize to negative infinity to accept any valid point initially
16 max_y_seen = -inf
17
18 # Check all points to the right (having greater or equal x-coordinate)
19 for x2, y2 in points[i + 1:]:
20 # A valid pair must satisfy:
21 # 1. y2 is greater than the highest y we've seen so far (no blocking)
22 # 2. y2 is less than or equal to y1 (within vertical bounds)
23 if max_y_seen < y2 <= y1:
24 # Update the maximum y-coordinate seen
25 max_y_seen = y2
26 # Count this as a valid pair
27 pair_count += 1
28
29 return pair_count
30
1class Solution {
2 public int numberOfPairs(int[][] points) {
3 // Sort points by x-coordinate ascending, if x is same then by y-coordinate descending
4 Arrays.sort(points, (a, b) -> a[0] == b[0] ? b[1] - a[1] : a[0] - b[0]);
5
6 int pairCount = 0;
7 int n = points.length;
8 final int NEGATIVE_INFINITY = 1 << 30; // Large negative value as initial maximum
9
10 // For each point as the left point of a potential pair
11 for (int i = 0; i < n; ++i) {
12 int leftPointY = points[i][1];
13 int maxYSeen = -NEGATIVE_INFINITY; // Track the maximum y-coordinate seen so far
14
15 // Check all points to the right as potential right points
16 for (int j = i + 1; j < n; ++j) {
17 int rightPointY = points[j][1];
18
19 // Valid pair if:
20 // 1. rightPointY is greater than the maximum Y seen so far (no point blocks it)
21 // 2. rightPointY is less than or equal to leftPointY (forms valid rectangle)
22 if (maxYSeen < rightPointY && rightPointY <= leftPointY) {
23 maxYSeen = rightPointY; // Update the maximum Y seen
24 ++pairCount; // Found a valid pair
25 }
26 }
27 }
28
29 return pairCount;
30 }
31}
32
1class Solution {
2public:
3 int numberOfPairs(vector<vector<int>>& points) {
4 // Sort points by x-coordinate (ascending), then by y-coordinate (descending) for same x
5 // This ensures we process points from left to right, and from top to bottom when x is same
6 sort(points.begin(), points.end(), [](const vector<int>& pointA, const vector<int>& pointB) {
7 if (pointA[0] != pointB[0]) {
8 return pointA[0] < pointB[0]; // Sort by x ascending
9 }
10 return pointA[1] > pointB[1]; // If x is same, sort by y descending
11 });
12
13 int totalPoints = points.size();
14 int validPairCount = 0;
15
16 // For each point as the left/upper point of a potential pair
17 for (int leftPointIdx = 0; leftPointIdx < totalPoints; ++leftPointIdx) {
18 int leftPointY = points[leftPointIdx][1];
19 int highestValidY = INT_MIN; // Track the highest y-coordinate seen among valid right points
20
21 // Check all points to the right as potential pair partners
22 for (int rightPointIdx = leftPointIdx + 1; rightPointIdx < totalPoints; ++rightPointIdx) {
23 int rightPointY = points[rightPointIdx][1];
24
25 // Valid pair conditions:
26 // 1. Right point's y must be <= left point's y (right point is not above left point)
27 // 2. Right point's y must be > highestValidY (no previous valid point blocks this one)
28 if (rightPointY <= leftPointY && rightPointY > highestValidY) {
29 highestValidY = rightPointY;
30 ++validPairCount;
31 }
32 }
33 }
34
35 return validPairCount;
36 }
37};
38
1/**
2 * Counts the number of valid point pairs where no other point lies in the rectangle formed by them
3 * @param points - Array of 2D points represented as [x, y] coordinates
4 * @returns The number of valid point pairs
5 */
6function numberOfPairs(points: number[][]): number {
7 // Sort points by x-coordinate (ascending), then by y-coordinate (descending) for ties
8 points.sort((pointA: number[], pointB: number[]) => {
9 if (pointA[0] === pointB[0]) {
10 return pointB[1] - pointA[1]; // If x-coordinates are equal, sort by y descending
11 }
12 return pointA[0] - pointB[0]; // Otherwise sort by x ascending
13 });
14
15 const totalPoints: number = points.length;
16 let validPairCount: number = 0;
17
18 // Iterate through each point as the left point of a potential pair
19 for (let leftIndex: number = 0; leftIndex < totalPoints; ++leftIndex) {
20 const [leftX, leftY]: number[] = points[leftIndex];
21 let highestValidY: number = -Infinity;
22
23 // Check all points to the right as potential right points of the pair
24 for (let rightIndex: number = leftIndex + 1; rightIndex < totalPoints; ++rightIndex) {
25 const [rightX, rightY]: number[] = points[rightIndex];
26
27 // Check if this forms a valid pair:
28 // - rightY must be greater than the highest Y seen so far (no point blocking)
29 // - rightY must be less than or equal to leftY (forms valid rectangle)
30 if (highestValidY < rightY && rightY <= leftY) {
31 highestValidY = rightY;
32 ++validPairCount;
33 }
34 }
35 }
36
37 return validPairCount;
38}
39
Time and Space Complexity
The time complexity of this code is O(n^2)
where n
is the number of points. This is because:
- The sorting operation takes
O(n log n)
time - The nested loops iterate through all pairs where
i < j
, resulting in approximatelyn * (n-1) / 2
iterations, which isO(n^2)
- Since
O(n^2)
dominatesO(n log n)
, the overall time complexity isO(n^2)
The space complexity is O(1)
if we don't count the space used by the sorting algorithm (which could be O(log n)
for the recursive call stack in typical implementations). The algorithm only uses a constant amount of extra space for variables ans
, max_y
, i
, y1
, and y2
, regardless of the input size.
Note: The reference answer stating O(1)
for time complexity appears to be incorrect, as the algorithm clearly has nested loops that depend on the input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrect Understanding of "Upper Left" Relationship
The Problem: A common mistake is misunderstanding what "upper left" means in a coordinate system. In standard 2D plane coordinates, y-values increase upward, so "upper left" means the point has a smaller x-coordinate AND a larger y-coordinate. Some developers might incorrectly assume "upper" means smaller y-values (thinking in terms of screen coordinates where y increases downward).
Example of Incorrect Logic:
# WRONG: Treating smaller y as "upper" if x1 <= x2 and y1 <= y2: # This is incorrect! # Process as valid upper-left relationship
Solution:
# CORRECT: Larger y is "upper" in standard coordinates if x1 <= x2 and y1 >= y2: # Point A has larger or equal y-coordinate # Process as valid upper-left relationship
Pitfall 2: Not Handling the Blocking Points Correctly
The Problem: When checking if a rectangle is empty, developers often forget that once a point B is found to be valid for point A, it creates a "barrier" - any subsequent point with a y-coordinate less than or equal to B's y-coordinate cannot form a valid pair with A because B would be inside their rectangle.
Example of Incorrect Logic:
# WRONG: Checking all other points for each pair independently
for i in range(n):
for j in range(i + 1, n):
if is_upper_left(points[i], points[j]):
valid = True
for k in range(n):
if k != i and k != j:
if point_in_rectangle(points[k], points[i], points[j]):
valid = False
break
if valid:
count += 1
This approach has O(n³) complexity and redundantly checks conditions.
Solution: Use the max_y tracking mechanism as shown in the original solution. Once we find a valid B point, we know that any point with y ≤ B.y cannot form a valid pair with the current A:
# CORRECT: Track maximum y to avoid redundant checks max_y_seen = -inf for x2, y2 in points[i + 1:]: if max_y_seen < y2 <= y1: # Only check if y2 is above our barrier max_y_seen = y2 pair_count += 1
Pitfall 3: Incorrect Sorting Strategy
The Problem: Using the wrong sorting order can make the algorithm incorrect or inefficient. A common mistake is sorting only by x-coordinate without considering y-coordinates for ties.
Example of Incorrect Logic:
# WRONG: Only sorting by x-coordinate points.sort(key=lambda p: p[0])
This fails when multiple points share the same x-coordinate. Without proper y-ordering, we might miss valid pairs or count invalid ones.
Solution: Sort by x-coordinate ascending, then by y-coordinate descending for ties:
# CORRECT: Sort by x ascending, then y descending points.sort(key=lambda p: (p[0], -p[1]))
This ensures that for points with the same x-coordinate, those with higher y-values come first, maintaining the correct processing order for the upper-left relationship.
How does merge sort divide the problem into subproblems?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!