3027. Find the Number of Ways to Place People II
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 problem defines directions as follows:
- Right: positive x-axis (increasing x-coordinate)
- Left: negative x-axis (decreasing x-coordinate)
- Up: positive y-axis (increasing y-coordinate)
- Down: negative y-axis (decreasing y-coordinate)
You need to place n
people, including Alice and Bob, at these points such that there is exactly one person at every point.
Alice wants to be alone with Bob, so she will build a rectangular fence with:
- Alice's position as the upper left corner
- Bob's position as the lower right corner
For Alice to be at the upper left corner and Bob at the lower right corner of the fence:
- Alice's x-coordinate must be ≤ Bob's x-coordinate
- Alice's y-coordinate must be ≥ Bob's y-coordinate
The fence might not enclose any area (it can be a line). If any person other than Alice and Bob is either inside the fence or on the fence boundary, Alice will be sad.
Your task is to return the number of pairs of points where you can place Alice and Bob such that Alice does not become sad when building the fence. In other words, count the valid (Alice, Bob) position pairs where no other person falls within or on the rectangular fence formed by these two positions.
Note: Alice can only build a fence with her position as the upper left corner and Bob's position as the lower right corner. The positions cannot be reversed.
Intuition
To find valid Alice-Bob pairs, we need to ensure no other person is inside or on the rectangular fence formed by their positions.
The key insight is that if we fix Alice's position and look for valid Bob positions, we need Bob to be to the right and below Alice (since Alice is upper-left, Bob is lower-right). This means for a point to be Bob's position relative to Alice at (x1, y1)
, Bob must be at some (x2, y2)
where x2 >= x1
and y2 <= y1
.
But how do we efficiently check if other people would be inside the fence? A rectangle with corners at (x1, y1)
and (x2, y2)
contains a point (x, y)
if x1 <= x <= x2
and y2 <= y <= y1
.
The clever approach is to sort the points in a specific way: by x-coordinate ascending, and for the same x-coordinate, by y-coordinate descending. This ordering (x, -y)
helps us process points from left to right, and within the same x-coordinate, from top to bottom.
Once sorted this way, for each point as Alice's position, we only need to check points that come after it in the sorted order (since they have x >= x_alice
). Among these candidates for Bob's position, we need those with y <= y_alice
.
The brilliant optimization comes from realizing that as we check potential Bob positions from left to right, we can maintain the maximum y-coordinate seen so far among valid Bob positions. If we encounter a point with y-coordinate between this maximum and Alice's y-coordinate, it forms a valid fence because:
- It satisfies the corner requirements (
x >= x_alice
,y <= y_alice
) - No previously checked points can be inside this fence (they either have smaller x-coordinates or their y-coordinates are outside the range)
This way, we avoid checking all possible pairs and all other points for each pair, reducing the complexity significantly.
Solution Approach
The implementation follows these steps:
-
Sort the points: We sort the points using a custom key
lambda x: (x[0], -x[1])
. This sorts by:- x-coordinate in ascending order (left to right)
- For same x-coordinate, y-coordinate in descending order (top to bottom)
-
Iterate through each point as Alice's position: We use the outer loop to fix Alice's position at index
i
with coordinates(_, y1)
. -
Initialize tracking variable: For each Alice position, we initialize
max_y = -inf
to track the maximum y-coordinate among valid Bob positions we've seen so far. -
Check potential Bob positions: We iterate through all points after Alice's position (
points[i + 1:]
) as potential Bob positions with coordinates(_, y2)
. -
Validate Bob's position: For each potential Bob position, we check if
max_y < y2 <= y1
:y2 <= y1
: Ensures Bob is below or at the same height as Alice (valid lower-right corner)y2 > max_y
: Ensures no previously considered point lies within the new fence
If both conditions are met:
- Update
max_y = y2
to track this as the new maximum y-coordinate - Increment
ans
counter as we found a valid Alice-Bob pair
-
Return the result: After checking all possible pairs, return the total count
ans
.
The algorithm works because:
- The sorting ensures we only check points to the right of Alice (satisfying the x-coordinate constraint)
- By maintaining
max_y
, we ensure that any point between Alice and Bob either:- Has x-coordinate less than Alice's (outside the fence on the left), or
- Has y-coordinate greater than Alice's or less than Bob's (outside the fence vertically)
- This guarantees no other person is inside or on the fence boundary
Time complexity: O(n^2)
where n is the number of points, as we have nested loops checking pairs.
Space complexity: O(1)
excluding the sorting space.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with points: [[1,1], [2,2], [3,3]]
Step 1: Sort the points
Using the key (x, -y)
, we sort the points:
[1,1]
→ key:(1, -1)
[2,2]
→ key:(2, -2)
[3,3]
→ key:(3, -3)
Sorted order: [[1,1], [2,2], [3,3]]
Step 2: Process each point as Alice's position
Alice at [1,1] (index 0):
- Initialize
max_y = -inf
- Check Bob at
[2,2]
: Is-inf < 2 ≤ 1
? No (2 > 1), skip - Check Bob at
[3,3]
: Is-inf < 3 ≤ 1
? No (3 > 1), skip - Found 0 valid pairs
Alice at [2,2] (index 1):
- Initialize
max_y = -inf
- Check Bob at
[3,3]
: Is-inf < 3 ≤ 2
? No (3 > 2), skip - Found 0 valid pairs
Alice at [3,3] (index 2):
- No points after this one
- Found 0 valid pairs
Total: 0 valid pairs
This makes sense! Since the points form an ascending diagonal line, no point is both to the right AND below another point, so no valid Alice-Bob fence can be formed.
Let's try another example with points: [[2,3], [3,1], [4,2]]
Step 1: Sort the points
[2,3]
→ key:(2, -3)
[3,1]
→ key:(3, -1)
[4,2]
→ key:(4, -2)
Sorted order: [[2,3], [3,1], [4,2]]
Step 2: Process each point as Alice's position
Alice at [2,3] (index 0):
- Initialize
max_y = -inf
- Check Bob at
[3,1]
: Is-inf < 1 ≤ 3
? Yes!- Update
max_y = 1
, increment count → 1 valid pair - Fence: upper-left (2,3) to lower-right (3,1), no other points inside
- Update
- Check Bob at
[4,2]
: Is1 < 2 ≤ 3
? Yes!- Update
max_y = 2
, increment count → 2 valid pairs - Fence: upper-left (2,3) to lower-right (4,2), point [3,1] is outside (y=1 < 2)
- Update
Alice at [3,1] (index 1):
- Initialize
max_y = -inf
- Check Bob at
[4,2]
: Is-inf < 2 ≤ 1
? No (2 > 1), skip - Found 0 valid pairs
Alice at [4,2] (index 2):
- No points after this one
- Found 0 valid pairs
Total: 2 valid pairs
- Alice at [2,3], Bob at [3,1]
- Alice at [2,3], Bob at [4,2]
The max_y
tracking ensures that when we place Bob at [4,2], the point [3,1] doesn't violate our fence because its y-coordinate (1) is less than Bob's y-coordinate (2), placing it outside the rectangular fence.
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, with higher points first
8 points.sort(key=lambda point: (point[0], -point[1]))
9
10 # Initialize counter for valid pairs
11 pair_count = 0
12
13 # Iterate through each point as the potential left point of a pair
14 for i, (x1, y1) in enumerate(points):
15 # Track the maximum y-coordinate seen so far for valid right points
16 # This helps avoid counting pairs where another point blocks the view
17 max_y_seen = -inf
18
19 # Check all points to the right of current point
20 for x2, y2 in points[i + 1:]:
21 # A valid pair must satisfy:
22 # 1. y2 is not higher than y1 (y2 <= y1)
23 # 2. y2 is higher than any previously seen valid point (y2 > max_y_seen)
24 # This ensures no other point blocks the rectangular region
25 if max_y_seen < y2 <= y1:
26 max_y_seen = y2
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 max
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;
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 max Y seen so far (no point blocks horizontally)
21 // 2. rightPointY is less than or equal to leftPointY (forms valid rectangle)
22 if (maxYSeen < rightPointY && rightPointY <= leftPointY) {
23 maxYSeen = rightPointY;
24 ++pairCount;
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), and if x-coordinates are equal,
5 // sort by y-coordinate (descending)
6 sort(points.begin(), points.end(), [](const vector<int>& a, const vector<int>& b) {
7 return a[0] < b[0] || (a[0] == b[0] && a[1] > b[1]);
8 });
9
10 int pointCount = points.size();
11 int validPairs = 0;
12
13 // For each point as the potential top-left corner
14 for (int i = 0; i < pointCount; ++i) {
15 int topLeftY = points[i][1];
16 int highestBottomRightY = INT_MIN;
17
18 // Check all points to the right as potential bottom-right corners
19 for (int j = i + 1; j < pointCount; ++j) {
20 int bottomRightY = points[j][1];
21
22 // Valid pair if:
23 // 1. Bottom-right Y is less than or equal to top-left Y
24 // 2. Bottom-right Y is higher than any previous valid bottom-right Y
25 // (ensures no points exist inside the rectangle)
26 if (highestBottomRightY < bottomRightY && bottomRightY <= topLeftY) {
27 highestBottomRightY = bottomRightY;
28 ++validPairs;
29 }
30 }
31 }
32
33 return validPairs;
34 }
35};
36
1/**
2 * Counts the number of valid pairs of points where no other point lies in the rectangle
3 * formed by the pair (excluding boundaries except the two points themselves)
4 * @param points - Array of 2D points [x, y]
5 * @returns Number of valid pairs
6 */
7function numberOfPairs(points: number[][]): number {
8 // Sort points by x-coordinate (ascending), then by y-coordinate (descending) for ties
9 points.sort((pointA: number[], pointB: number[]) => {
10 if (pointA[0] === pointB[0]) {
11 return pointB[1] - pointA[1];
12 }
13 return pointA[0] - pointB[0];
14 });
15
16 const totalPoints: number = points.length;
17 let validPairCount: number = 0;
18
19 // Iterate through each point as the left point of a potential pair
20 for (let leftIndex: number = 0; leftIndex < totalPoints; ++leftIndex) {
21 const [leftX, leftY]: number[] = points[leftIndex];
22 let highestValidY: number = -Infinity;
23
24 // Check all points to the right as potential right points of the pair
25 for (let rightIndex: number = leftIndex + 1; rightIndex < totalPoints; ++rightIndex) {
26 const [rightX, rightY]: number[] = points[rightIndex];
27
28 // Check if this point forms a valid pair with the left point
29 // Valid if: rightY is higher than any previous valid right point
30 // and rightY is not above leftY
31 if (highestValidY < rightY && rightY <= leftY) {
32 highestValidY = rightY;
33 ++validPairCount;
34 }
35 }
36 }
37
38 return validPairCount;
39}
40
Time and Space Complexity
The time complexity of this code is O(n^2)
, where n
is the number of points. The sorting operation takes O(n log n)
time, and the nested loops iterate through all pairs of points with i < j
, resulting in O(n^2)
comparisons. Since O(n^2)
dominates O(n log n)
, the overall time complexity is O(n^2)
.
The space complexity is O(1)
if we don't count the space used by the sorting algorithm (which might use O(log n)
for in-place sorting). 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
1. Incorrect Sorting Logic
Pitfall: Using the wrong sorting order, particularly sorting y-coordinates in ascending order instead of descending order.
# WRONG - sorts y in ascending order points.sort(key=lambda point: (point[0], point[1]))
This breaks the algorithm because when we process points with the same x-coordinate, we need to consider higher points first to correctly maintain the max_y_seen
invariant.
Solution: Always sort with (x, -y)
to ensure proper ordering:
# CORRECT - sorts x ascending, y descending points.sort(key=lambda point: (point[0], -point[1]))
2. Misunderstanding the Fence Constraints
Pitfall: Incorrectly assuming Alice must be strictly above and to the left of Bob, using strict inequalities:
# WRONG - uses strict inequality if max_y_seen < y2 < y1: # Missing edge case where y2 == y1 max_y_seen = y2 pair_count += 1
This misses valid cases where Alice and Bob can be at the same height (forming a horizontal line fence).
Solution: Use <=
for the y-coordinate comparison:
# CORRECT - allows Alice and Bob at same height if max_y_seen < y2 <= y1: max_y_seen = y2 pair_count += 1
3. Not Updating max_y_seen for Invalid Pairs
Pitfall: Only updating max_y_seen
when finding a valid pair:
# WRONG - can miss blocking points for x2, y2 in points[i + 1:]: if max_y_seen < y2 <= y1: max_y_seen = y2 pair_count += 1 # Should we update max_y_seen for points with y2 > y1?
While the current solution is correct (we only need to track max_y for valid Bob positions), some might mistakenly think they need to track ALL points, which would lead to:
# UNNECESSARY - tracking all points
for x2, y2 in points[i + 1:]:
if y2 <= y1 and y2 > max_y_seen:
pair_count += 1
max_y_seen = max(max_y_seen, y2) # This would be wrong!
Solution: Only update max_y_seen
for valid Bob positions, as points above Alice are already outside the fence.
4. Forgetting Edge Cases
Pitfall: Not considering that the fence can be a single point or a line:
- When Alice and Bob are at the same position (if allowed)
- When the fence forms a vertical or horizontal line
Solution: The condition max_y_seen < y2 <= y1
correctly handles these cases:
- Horizontal line: when
y2 == y1
- Vertical line: when points have the same x-coordinate (handled by sorting)
Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?
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!