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 columny
) - At least one building to its right (same row
x
, larger columny
) - At least one building above it (same column
y
, larger rowx
) - At least one building below it (same column
y
, smaller rowx
)
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]
wherey < 3
(left) - There's at least one building at
[2, y]
wherey > 3
(right) - There's at least one building at
[x, 3]
wherex > 2
(above) - There's at least one building at
[x, 3]
wherex < 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.
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 differenty
values - Buildings above and below share the same column
y
but have differentx
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 rowx
are stored ing1[x]
as a list of y-coordinates - All buildings in column
y
are stored ing2[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 ing1[x]
, it has neighbors left and right - If the building's
x
value is between the minimum and maximum ing2[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 rowg2
: 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:
- Get the sorted list of y-coordinates in row
x
:l1 = g1[x]
- Get the sorted list of x-coordinates in column
y
:l2 = g2[y]
- 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 EvaluatorExample 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
-
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 β
-
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 β
-
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)
-
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 β
-
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
andg2
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 lengthn
to sort, which takesO(n log n)
. In the best case, each x-coordinate has only one building, resulting inn
lists of length 1, which takesO(n)
total. Overall, the sorting step isO(n log n)
in the worst case. - Sorting all lists in
g2
: Similarly, this takesO(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 mostn
building coordinates across all its lists:O(n)
- Dictionary
g2
stores at mostn
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 rowx
)" - "below it (same column
y
, smaller rowx
)"
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:
- Mathematical convention: Origin at bottom-left, y increases upward
- Computer graphics convention: Origin at top-left, y increases downward
- 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.
What are the most two important steps in writing a depth first search function? (Select 2)
Recommended Readings
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!