Facebook Pixel

3531. Count Covered Buildings

Problem Description

You have an n x n city grid and a list of buildings with their coordinates. Each building is represented as [x, y] where x and y are its coordinates on the grid.

A building is considered covered if there exists at least one other building in each of the four directions:

  • At least one building to its left (same row x, smaller column y)
  • At least one building to its right (same row x, larger column y)
  • At least one building above it (same column y, larger row x)
  • At least one building below it (same column y, smaller row x)

Your task is to count how many buildings are covered according to this definition.

For example, if a building is at position [2, 3], it would be covered only if:

  • There's at least one building at [2, y] where y < 3 (left)
  • There's at least one building at [2, y] where y > 3 (right)
  • There's at least one building at [x, 3] where x > 2 (above)
  • There's at least one building at [x, 3] where x < 2 (below)

The solution groups buildings by their x-coordinates and y-coordinates using hash tables. For each building (x, y), it checks if there are buildings both before and after it in the same row (checking g1[x] for y-values) and buildings both before and after it in the same column (checking g2[y] for x-values). If both conditions are met, the building is covered.

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

Intuition

The key insight is that to check if a building is covered, we need to efficiently determine if there are buildings in all four directions from it. Instead of checking every possible pair of buildings (which would be inefficient), we can organize the data to make these checks fast.

Think about what it means for a building at (x, y) to have neighbors in all four directions:

  • Buildings to the left and right share the same row x but have different y values
  • Buildings above and below share the same column y but have different x values

This naturally leads us to group buildings by their shared coordinates. If we group all buildings that share the same x-coordinate together, we can quickly find all buildings in the same row. Similarly, grouping by y-coordinate gives us all buildings in the same column.

Once we have these groupings:

  • For a building at (x, y), all buildings in row x are stored in g1[x] as a list of y-coordinates
  • All buildings in column y are stored in g2[y] as a list of x-coordinates

By sorting these lists, we can use a simple comparison to check coverage:

  • If the building's y value is between the minimum and maximum in g1[x], it has neighbors left and right
  • If the building's x value is between the minimum and maximum in g2[y], it has neighbors above and below

The condition l2[0] < x < l2[-1] checks if x is strictly between the smallest and largest x-coordinates in column y, confirming vertical neighbors. Similarly, l1[0] < y < l1[-1] confirms horizontal neighbors. When both conditions are true, the building is covered.

Learn more about Sorting patterns.

Solution Approach

The implementation uses hash tables and sorting to efficiently determine covered buildings.

Step 1: Group buildings by coordinates

We create two hash tables using defaultdict(list):

  • g1: Maps each x-coordinate to a list of y-coordinates of buildings in that row
  • g2: Maps each y-coordinate to a list of x-coordinates of buildings in that column
g1 = defaultdict(list)
g2 = defaultdict(list)
for x, y in buildings:
    g1[x].append(y)
    g2[y].append(x)

For example, if we have buildings at [2,3], [2,5], and [4,3]:

  • g1[2] would contain [3, 5] (all y-values for row 2)
  • g2[3] would contain [2, 4] (all x-values for column 3)

Step 2: Sort the coordinate lists

We sort each list to easily identify the minimum and maximum values:

for x in g1:
    g1[x].sort()
for y in g2:
    g2[y].sort()

After sorting, the first element [0] is the minimum and the last element [-1] is the maximum.

Step 3: Check each building for coverage

For each building (x, y), we:

  1. Get the sorted list of y-coordinates in row x: l1 = g1[x]
  2. Get the sorted list of x-coordinates in column y: l2 = g2[y]
  3. Check if the building is covered using the conditions:
    • l2[0] < x < l2[-1]: There are buildings both above (x > l2[0]) and below (x < l2[-1])
    • l1[0] < y < l1[-1]: There are buildings both to the left (y > l1[0]) and right (y < l1[-1])
ans = 0
for x, y in buildings:
    l1 = g1[x]
    l2 = g2[y]
    if l2[0] < x < l2[-1] and l1[0] < y < l1[-1]:
        ans += 1

The time complexity is O(n log n) where n is the number of buildings, due to the sorting operations. The space complexity is O(n) for storing the hash tables.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example with buildings at coordinates: [[1,2], [2,1], [2,2], [2,3], [3,2]]

Visualizing on a grid (with 'B' marking buildings):

  0 1 2 3
3       B
2   B B B
1     B
0

Step 1: Group buildings by coordinates

We create two hash tables:

  • g1 (group by x-coordinate/row):

    • g1[1] = [2] (row 1 has building at column 2)
    • g1[2] = [1, 2, 3] (row 2 has buildings at columns 1, 2, 3)
    • g1[3] = [2] (row 3 has building at column 2)
  • g2 (group by y-coordinate/column):

    • g2[1] = [2] (column 1 has building at row 2)
    • g2[2] = [1, 2, 3] (column 2 has buildings at rows 1, 2, 3)
    • g2[3] = [2] (column 3 has building at row 2)

Step 2: Sort the coordinate lists

After sorting (already sorted in this case):

  • g1[1] = [2], g1[2] = [1, 2, 3], g1[3] = [2]
  • g2[1] = [2], g2[2] = [1, 2, 3], g2[3] = [2]

Step 3: Check each building for coverage

  1. Building at [1,2]:

    • l1 = g1[1] = [2] (buildings in row 1)
    • l2 = g2[2] = [1, 2, 3] (buildings in column 2)
    • Check: 1 < 1 < 3? Yes. 2 < 2 < 2? No (need strict inequality)
    • Not covered ❌
  2. Building at [2,1]:

    • l1 = g1[2] = [1, 2, 3] (buildings in row 2)
    • l2 = g2[1] = [2] (buildings in column 1)
    • Check: 2 < 2 < 2? No. 1 < 1 < 3? No
    • Not covered ❌
  3. Building at [2,2]:

    • l1 = g1[2] = [1, 2, 3] (buildings in row 2)
    • l2 = g2[2] = [1, 2, 3] (buildings in column 2)
    • Check: 1 < 2 < 3? Yes. 1 < 2 < 3? Yes
    • Covered! βœ“ (has buildings in all 4 directions)
  4. Building at [2,3]:

    • l1 = g1[2] = [1, 2, 3] (buildings in row 2)
    • l2 = g2[3] = [2] (buildings in column 3)
    • Check: 2 < 2 < 2? No. 1 < 3 < 3? No
    • Not covered ❌
  5. Building at [3,2]:

    • l1 = g1[3] = [2] (buildings in row 3)
    • l2 = g2[2] = [1, 2, 3] (buildings in column 2)
    • Check: 1 < 3 < 3? No. 2 < 2 < 2? No
    • Not covered ❌

Result: Only 1 building (at [2,2]) is covered.

The building at [2,2] is covered because:

  • Left: building at [2,1] (same row, smaller y)
  • Right: building at [2,3] (same row, larger y)
  • Below: building at [1,2] (same column, smaller x)
  • Above: building at [3,2] (same column, larger x)

Solution Implementation

1from collections import defaultdict
2from typing import List
3
4class Solution:
5    def countCoveredBuildings(self, n: int, buildings: List[List[int]]) -> int:
6        # Group buildings by their x-coordinate (vertical lines)
7        buildings_by_x = defaultdict(list)
8        # Group buildings by their y-coordinate (horizontal lines)
9        buildings_by_y = defaultdict(list)
10      
11        # Populate the groupings
12        for x_coord, y_coord in buildings:
13            buildings_by_x[x_coord].append(y_coord)
14            buildings_by_y[y_coord].append(x_coord)
15      
16        # Sort y-coordinates for each x group (for finding min/max efficiently)
17        for x_coord in buildings_by_x:
18            buildings_by_x[x_coord].sort()
19      
20        # Sort x-coordinates for each y group (for finding min/max efficiently)
21        for y_coord in buildings_by_y:
22            buildings_by_y[y_coord].sort()
23      
24        covered_count = 0
25      
26        # Check each building to see if it's covered
27        for x_coord, y_coord in buildings:
28            # Get all y-coordinates for buildings with same x
29            y_coords_at_x = buildings_by_x[x_coord]
30            # Get all x-coordinates for buildings with same y
31            x_coords_at_y = buildings_by_y[y_coord]
32          
33            # A building is covered if:
34            # 1. There are buildings with same y-coordinate both to its left and right
35            # 2. There are buildings with same x-coordinate both above and below it
36            if (x_coords_at_y[0] < x_coord < x_coords_at_y[-1] and 
37                y_coords_at_x[0] < y_coord < y_coords_at_x[-1]):
38                covered_count += 1
39      
40        return covered_count
41
1class Solution {
2    public int countCoveredBuildings(int n, int[][] buildings) {
3        // Map to store all y-coordinates for each x-coordinate
4        Map<Integer, List<Integer>> buildingsAtX = new HashMap<>();
5        // Map to store all x-coordinates for each y-coordinate
6        Map<Integer, List<Integer>> buildingsAtY = new HashMap<>();
7
8        // Group buildings by their x and y coordinates
9        for (int[] building : buildings) {
10            int xCoord = building[0];
11            int yCoord = building[1];
12          
13            // Add y-coordinate to the list of buildings at this x
14            buildingsAtX.computeIfAbsent(xCoord, k -> new ArrayList<>()).add(yCoord);
15            // Add x-coordinate to the list of buildings at this y
16            buildingsAtY.computeIfAbsent(yCoord, k -> new ArrayList<>()).add(xCoord);
17        }
18
19        // Sort all y-coordinates for each x to find min and max efficiently
20        for (Map.Entry<Integer, List<Integer>> entry : buildingsAtX.entrySet()) {
21            Collections.sort(entry.getValue());
22        }
23      
24        // Sort all x-coordinates for each y to find min and max efficiently
25        for (Map.Entry<Integer, List<Integer>> entry : buildingsAtY.entrySet()) {
26            Collections.sort(entry.getValue());
27        }
28
29        int coveredBuildingCount = 0;
30
31        // Check each building to see if it's covered
32        for (int[] building : buildings) {
33            int xCoord = building[0];
34            int yCoord = building[1];
35          
36            // Get all buildings on the same vertical line (same x)
37            List<Integer> buildingsOnSameX = buildingsAtX.get(xCoord);
38            // Get all buildings on the same horizontal line (same y)
39            List<Integer> buildingsOnSameY = buildingsAtY.get(yCoord);
40
41            // Check if building is covered:
42            // - There must be buildings to the left and right (on same horizontal line)
43            // - There must be buildings above and below (on same vertical line)
44            boolean hasLeftBuilding = buildingsOnSameY.get(0) < xCoord;
45            boolean hasRightBuilding = xCoord < buildingsOnSameY.get(buildingsOnSameY.size() - 1);
46            boolean hasBelowBuilding = buildingsOnSameX.get(0) < yCoord;
47            boolean hasAboveBuilding = yCoord < buildingsOnSameX.get(buildingsOnSameX.size() - 1);
48          
49            if (hasLeftBuilding && hasRightBuilding && hasBelowBuilding && hasAboveBuilding) {
50                coveredBuildingCount++;
51            }
52        }
53
54        return coveredBuildingCount;
55    }
56}
57
1class Solution {
2public:
3    int countCoveredBuildings(int n, vector<vector<int>>& buildings) {
4        // Map to store all y-coordinates for each x-coordinate
5        unordered_map<int, vector<int>> buildingsAtX;
6        // Map to store all x-coordinates for each y-coordinate
7        unordered_map<int, vector<int>> buildingsAtY;
8
9        // Group buildings by their x and y coordinates
10        for (const auto& building : buildings) {
11            int xCoord = building[0];
12            int yCoord = building[1];
13            buildingsAtX[xCoord].push_back(yCoord);
14            buildingsAtY[yCoord].push_back(xCoord);
15        }
16
17        // Sort all y-coordinates for each x-coordinate group
18        for (auto& [x, yCoords] : buildingsAtX) {
19            sort(yCoords.begin(), yCoords.end());
20        }
21      
22        // Sort all x-coordinates for each y-coordinate group
23        for (auto& [y, xCoords] : buildingsAtY) {
24            sort(xCoords.begin(), xCoords.end());
25        }
26
27        int coveredBuildingCount = 0;
28
29        // Check each building to see if it's covered
30        for (const auto& building : buildings) {
31            int xCoord = building[0];
32            int yCoord = building[1];
33          
34            // Get all buildings with same x-coordinate (vertical line)
35            const vector<int>& yCoordinatesAtSameX = buildingsAtX[xCoord];
36            // Get all buildings with same y-coordinate (horizontal line)
37            const vector<int>& xCoordinatesAtSameY = buildingsAtY[yCoord];
38
39            // Check if building is covered:
40            // - There exists a building to the left and right (on same y-coordinate)
41            // - There exists a building above and below (on same x-coordinate)
42            bool hasLeftBuilding = xCoordinatesAtSameY[0] < xCoord;
43            bool hasRightBuilding = xCoord < xCoordinatesAtSameY.back();
44            bool hasBottomBuilding = yCoordinatesAtSameX[0] < yCoord;
45            bool hasTopBuilding = yCoord < yCoordinatesAtSameX.back();
46          
47            if (hasLeftBuilding && hasRightBuilding && hasBottomBuilding && hasTopBuilding) {
48                coveredBuildingCount++;
49            }
50        }
51
52        return coveredBuildingCount;
53    }
54};
55
1/**
2 * Counts the number of buildings that are covered by other buildings
3 * in both horizontal and vertical directions
4 * @param n - The grid size (unused in this implementation)
5 * @param buildings - Array of building coordinates [x, y]
6 * @returns Number of buildings that are covered
7 */
8function countCoveredBuildings(n: number, buildings: number[][]): number {
9    // Map to store all y-coordinates for each x-coordinate
10    const xToYCoordinates: Map<number, number[]> = new Map();
11    // Map to store all x-coordinates for each y-coordinate
12    const yToXCoordinates: Map<number, number[]> = new Map();
13
14    // Build the coordinate mappings
15    for (const [xCoord, yCoord] of buildings) {
16        // Add y-coordinate to the list for this x-coordinate
17        if (!xToYCoordinates.has(xCoord)) {
18            xToYCoordinates.set(xCoord, []);
19        }
20        xToYCoordinates.get(xCoord)?.push(yCoord);
21
22        // Add x-coordinate to the list for this y-coordinate
23        if (!yToXCoordinates.has(yCoord)) {
24            yToXCoordinates.set(yCoord, []);
25        }
26        yToXCoordinates.get(yCoord)?.push(xCoord);
27    }
28
29    // Sort all coordinate lists in ascending order
30    for (const yCoordList of xToYCoordinates.values()) {
31        yCoordList.sort((a, b) => a - b);
32    }
33    for (const xCoordList of yToXCoordinates.values()) {
34        xCoordList.sort((a, b) => a - b);
35    }
36
37    let coveredBuildingsCount = 0;
38
39    // Check each building to see if it's covered
40    for (const [xCoord, yCoord] of buildings) {
41        // Get all y-coordinates on the same vertical line (same x)
42        const yCoordinatesOnSameX = xToYCoordinates.get(xCoord)!;
43        // Get all x-coordinates on the same horizontal line (same y)
44        const xCoordinatesOnSameY = yToXCoordinates.get(yCoord)!;
45
46        // Check if the building is between other buildings both horizontally and vertically
47        const isBetweenHorizontally = xCoordinatesOnSameY[0] < xCoord && 
48                                      xCoord < xCoordinatesOnSameY[xCoordinatesOnSameY.length - 1];
49        const isBetweenVertically = yCoordinatesOnSameX[0] < yCoord && 
50                                    yCoord < yCoordinatesOnSameX[yCoordinatesOnSameX.length - 1];
51
52        if (isBetweenHorizontally && isBetweenVertically) {
53            coveredBuildingsCount++;
54        }
55    }
56
57    return coveredBuildingsCount;
58}
59

Time and Space Complexity

The time complexity is O(n Γ— log n), where n is the number of buildings.

Breaking down the time complexity:

  • Creating the dictionaries g1 and g2 by iterating through all buildings: O(n)
  • Sorting all lists in g1: In the worst case, all buildings share the same x-coordinate, resulting in one list of length n to sort, which takes O(n log n). In the best case, each x-coordinate has only one building, resulting in n lists of length 1, which takes O(n) total. Overall, the sorting step is O(n log n) in the worst case.
  • Sorting all lists in g2: Similarly, this takes O(n log n) in the worst case.
  • Final loop through all buildings: O(n), with each iteration performing constant-time operations (accessing first and last elements of sorted lists and comparisons).

The dominant operation is the sorting step, giving us O(n log n) overall time complexity.

The space complexity is O(n), where n is the number of buildings.

Breaking down the space complexity:

  • Dictionary g1 stores at most n building coordinates across all its lists: O(n)
  • Dictionary g2 stores at most n building coordinates across all its lists: O(n)
  • The total auxiliary space used is O(n) + O(n) = O(n)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Confusion About Coordinate System Interpretation

The Problem: A major source of confusion in this problem is the inconsistent interpretation of the coordinate system between the problem description and the actual solution. The problem states:

  • "above it (same column y, larger row x)"
  • "below it (same column y, smaller row x)"

However, the solution code uses the opposite logic:

if l2[0] < x < l2[-1] and l1[0] < y < l1[-1]:

This checks if x is between the minimum and maximum x-values (treating smaller x as "below" and larger x as "above"), which contradicts the problem statement.

Why This Happens: Different coordinate systems can be used:

  1. Mathematical convention: Origin at bottom-left, y increases upward
  2. Computer graphics convention: Origin at top-left, y increases downward
  3. Matrix indexing: Row index increases downward

The Solution: Always clarify the coordinate system before implementing. If the problem states "larger row x means above," ensure your code reflects this:

# Corrected version matching problem description
for x_coord, y_coord in buildings:
    y_coords_at_x = buildings_by_x[x_coord]
    x_coords_at_y = buildings_by_y[y_coord]
  
    # According to problem: larger x = above, smaller x = below
    has_below = any(x < x_coord for x in x_coords_at_y)  # smaller x
    has_above = any(x > x_coord for x in x_coords_at_y)  # larger x
    has_left = any(y < y_coord for y in y_coords_at_x)   # smaller y
    has_right = any(y > y_coord for y in y_coords_at_x)  # larger y
  
    if has_below and has_above and has_left and has_right:
        covered_count += 1

Pitfall 2: Edge Case with Single Building in Row or Column

The Problem: When there's only one building in a row or column, the sorted list will have length 1. Accessing list[0] and list[-1] will return the same value, making the condition list[0] < coordinate < list[-1] always false.

Example: If building at [3, 5] is the only building in row 3:

  • buildings_by_x[3] = [5]
  • The check 5 < 5 < 5 is always false

The Solution: Add an explicit check for minimum required buildings:

for x_coord, y_coord in buildings:
    y_coords_at_x = buildings_by_x[x_coord]
    x_coords_at_y = buildings_by_y[y_coord]
  
    # Need at least 3 buildings in each direction to have one covered
    if len(y_coords_at_x) < 3 or len(x_coords_at_y) < 3:
        continue
  
    if (x_coords_at_y[0] < x_coord < x_coords_at_y[-1] and 
        y_coords_at_x[0] < y_coord < y_coords_at_x[-1]):
        covered_count += 1

This optimization also improves performance by skipping unnecessary checks for buildings that cannot possibly be covered.

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

What are the most two important steps in writing a depth first search function? (Select 2)


Recommended Readings

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

Load More