Facebook Pixel

3025. Find the Number of Ways to Place People I

MediumGeometryArrayMathEnumerationSorting
Leetcode Link

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:

  1. Point A is on the upper left side of point B. This means:

    • The x-coordinate of A is less than or equal to the x-coordinate of B (A.x <= B.x)
    • The y-coordinate of A is greater than or equal to the y-coordinate of B (A.y >= B.y)
  2. There are no other points inside or on the boundary of the rectangle formed by points A and B. The rectangle has A as the upper-left corner and B 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Any point with a higher y-coordinate than B.y would need to be checked, but if B.y > max_y, we haven't seen any such interfering point yet
  2. 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.

Learn more about Math and Sorting patterns.

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 point B is not above point A
  • 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 Evaluator

Example 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, because 2 > 1 (B.y must be ≤ A.y)
      • Not a valid pair
    • B = [3,3] (index 2):
      • Is max_y < 3 <= 1? No, because 3 > 1
      • Not a valid pair
  • 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, because 3 > 2
      • Not a valid pair
  • 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, because 0 < 2
      • Not valid (point [3,2] would be inside the rectangle)

Iteration 2: A = [2,1] (index 1)

  • Initialize max_y = -inf
  • Check subsequent points as B:
    • B = [3,2]: Is -inf < 2 <= 1? No, because 2 > 1
    • B = [4,0]: Is -inf < 0 <= 1? Yes!
      • Valid pair: ([2,1], [4,0])
      • Update max_y = 0, count = 3

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

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 approximately n * (n-1) / 2 iterations, which is O(n^2)
  • 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 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.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

How does merge sort divide the problem into subproblems?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More