3531. Count Covered Buildings
Problem Description
You are given a positive integer n
, representing an n x n
city. Additionally, you receive a 2D grid buildings
, where each element buildings[i] = [x, y]
denotes a unique building located at coordinates [x, y]
.
A building is considered covered if there is at least one other building in all four cardinal directions: left, right, above, and below.
Your task is to determine and return the number of covered buildings.
Intuition
To solve the problem of counting covered buildings, we employ a combination of data structures to efficiently manage and access building coordinates. The key idea is to categorize the buildings by their x and y coordinates using hash tables, then sort these coordinates to quickly check if a building is covered.
-
Hash Tables for Categorization: We use two hash tables,
g1
andg2
.g1[x]
stores all the y-coordinates of buildings with the same x-coordinatex
. Similarly,g2[y]
keeps all the x-coordinates of buildings sharing the y-coordinatey
. This sets the groundwork for categorizing and accessing buildings based on their respective axes. -
Sorting for Efficient Checking: Once categorized, we sort the lists in both hash tables. Sorting helps easily determine if the current building
(x, y)
has neighbors in the required directions by checking the minimum and maximum values in the sorted lists. -
Checking Coverage Condition: For a building
(x, y)
to be covered, it should have buildings both to its left and right (on the same y-coordinate), and buildings both above and below it (on the same x-coordinate). Mathematically, this translates to checking if:l2[0] < x < l2[-1]
wherel2
is the sorted list of x-coordinates at y.l1[0] < y < l1[-1]
wherel1
is the sorted list of y-coordinates at x.
By looping through each building and applying the above checks, we accumulate the count of covered buildings.
This method ensures an efficient traversal of potential candidate buildings, making it feasible to work within the constraints typically encountered in computational problems.
Learn more about Sorting patterns.
Solution Approach
The solution for counting covered buildings involves a strategic use of hash tables and sorting. Here's a step-by-step walkthrough of the implementation:
-
Data Structures Setup:
- We initialize two hash tables,
g1
andg2
, using Python'sdefaultdict
from thecollections
module. These will help in grouping buildings by their coordinates. g1[x]
will hold a list of all y-coordinates for buildings with x-coordinatex
.g2[y]
will contain a list of all x-coordinates for buildings sharing the y-coordinatey
.
- We initialize two hash tables,
-
Populating Hash Tables:
- Iterate through each building's coordinates
(x, y)
in the givenbuildings
list. - For each building, append the y-coordinate
y
tog1[x]
and the x-coordinatex
tog2[y]
.
- Iterate through each building's coordinates
-
Sorting for Quick Access:
- After populating the hash tables, sort the lists in both
g1
andg2
. This enables quick determination of neighboring buildings along the same line of coordinate.
- After populating the hash tables, sort the lists in both
-
Counting Covered Buildings:
- Initialize a counter
ans
to zero, which will store the number of covered buildings. - Loop over each building
(x, y)
again. - Retrieve the sorted list
l1
fromg1[x]
, which contains y-coordinates, andl2
fromg2[y]
, which contains x-coordinates. - Check if the conditions for being covered are met:
l2[0] < x < l2[-1]
ensures there are buildings both on the left and right.l1[0] < y < l1[-1]
ensures there are buildings both above and below.
- If both conditions are satisfied, increment the
ans
counter.
- Initialize a counter
-
Return the Result:
- Once all buildings are processed, the final count stored in
ans
is returned as the result.
- Once all buildings are processed, the final count stored in
This approach efficiently determines which buildings are covered by leveraging the combined power of hash tables for fast categorization and sorting for concise condition checking.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Consider a simple 3 x 3
grid with three buildings located at the following coordinates:
- Building 1:
(1, 1)
- Building 2:
(1, 2)
- Building 3:
(2, 2)
The grid visualization will look like this:
y | v 3 . . . 2 . B C 1 . A . 1 2 3 x
Where A
, B
, and C
represent buildings.
Step 1: Data Structures Setup
- Initialize two hash tables using
defaultdict
:g1
andg2
.
Step 2: Populating Hash Tables
- For building
(1, 1)
,g1[1] = [1]
andg2[1] = [1]
. - For building
(1, 2)
,g1[1] = [1, 2]
andg2[2] = [1]
. - For building
(2, 2)
,g1[2] = [2]
andg2[2] = [1, 2]
.
Step 3: Sorting for Quick Access
- Sort the coordinates in
g1
andg2
:g1[1]
remains[1, 2]
.g1[2]
remains[2]
.g2[1]
remains[1]
.g2[2]
is sorted as[1, 2]
.
Step 4: Counting Covered Buildings
-
Initialize counter
ans = 0
. -
Check building
(1, 1)
:l1 = [1, 2]
fromg1[1]
andl2 = [1]
fromg2[1]
.- Condition
l1[0] < y < l1[-1]
fails as1 < 1 < 2
is false. - Building
(1, 1)
is not covered.
-
Check building
(1, 2)
:l1 = [1, 2]
fromg1[1]
andl2 = [1, 2]
fromg2[2]
.- Condition
l1[0] < y < l1[-1]
fails as1 < 2 < 2
is false. - Building
(1, 2)
is not covered.
-
Check building
(2, 2)
:l1 = [2]
fromg1[2]
andl2 = [1, 2]
fromg2[2]
.- Condition
l2[0] < x < l2[-1]
fails as1 < 2 < 2
is false. - Building
(2, 2)
is not covered.
Step 5: Return the Result
- The final count of covered buildings is
ans = 0
.
Thus, none of the buildings are covered in this particular setup.
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 # g1 and g2 are dictionaries where keys are x or y coordinates
7 # and values are lists of corresponding y or x coordinates.
8 x_to_y_coordinates = defaultdict(list)
9 y_to_x_coordinates = defaultdict(list)
10
11 # Populate x_to_y_coordinates and y_to_x_coordinates mappings
12 for x, y in buildings:
13 x_to_y_coordinates[x].append(y)
14 y_to_x_coordinates[y].append(x)
15
16 # Sort lists of coordinates for each x and y
17 for x in x_to_y_coordinates:
18 x_to_y_coordinates[x].sort()
19 for y in y_to_x_coordinates:
20 y_to_x_coordinates[y].sort()
21
22 # Initialize the count of covered buildings
23 count = 0
24
25 # Check each building to see if it is covered
26 for x, y in buildings:
27 list_y_for_x = x_to_y_coordinates[x]
28 list_x_for_y = y_to_x_coordinates[y]
29
30 # A building is covered if there are both larger and smaller x values
31 # for the same y, and both larger and smaller y values for the same x
32 if list_x_for_y[0] < x < list_x_for_y[-1] and list_y_for_x[0] < y < list_y_for_x[-1]:
33 count += 1
34
35 return count
36
1import java.util.*;
2
3class Solution {
4 public int countCoveredBuildings(int n, int[][] buildings) {
5 // Maps to store buildings by x and y coordinates
6 Map<Integer, List<Integer>> buildingsByX = new HashMap<>();
7 Map<Integer, List<Integer>> buildingsByY = new HashMap<>();
8
9 // Populate the maps with building coordinates
10 for (int[] building : buildings) {
11 int x = building[0];
12 int y = building[1];
13
14 // For x coordinate, add y to the list
15 buildingsByX.computeIfAbsent(x, k -> new ArrayList<>()).add(y);
16
17 // For y coordinate, add x to the list
18 buildingsByY.computeIfAbsent(y, k -> new ArrayList<>()).add(x);
19 }
20
21 // Sort each list of y-coordinates for a given x
22 for (var entry : buildingsByX.entrySet()) {
23 Collections.sort(entry.getValue());
24 }
25 // Sort each list of x-coordinates for a given y
26 for (var entry : buildingsByY.entrySet()) {
27 Collections.sort(entry.getValue());
28 }
29
30 int coveredBuildingsCount = 0;
31
32 // Check each building to determine if it is covered
33 for (int[] building : buildings) {
34 int x = building[0];
35 int y = building[1];
36
37 // Retrieve sorted lists of x and y coordinates
38 List<Integer> yCoordinatesAtX = buildingsByX.get(x);
39 List<Integer> xCoordinatesAtY = buildingsByY.get(y);
40
41 // Check if the current building is actually covered
42 if (xCoordinatesAtY.get(0) < x && x < xCoordinatesAtY.get(xCoordinatesAtY.size() - 1) &&
43 yCoordinatesAtX.get(0) < y && y < yCoordinatesAtX.get(yCoordinatesAtX.size() - 1)) {
44 coveredBuildingsCount++;
45 }
46 }
47
48 return coveredBuildingsCount; // Return the count of covered buildings
49 }
50}
51
1#include <vector>
2#include <unordered_map>
3#include <algorithm>
4
5using namespace std;
6
7class Solution {
8public:
9 int countCoveredBuildings(int n, vector<vector<int>>& buildings) {
10 // g1 maps x-coordinates to their associated y-coordinates
11 unordered_map<int, vector<int>> xToYCoordinates;
12 // g2 maps y-coordinates to their associated x-coordinates
13 unordered_map<int, vector<int>> yToXCoordinates;
14
15 // Build the mappings from coordinates
16 for (const auto& building : buildings) {
17 int x = building[0];
18 int y = building[1];
19 xToYCoordinates[x].push_back(y);
20 yToXCoordinates[y].push_back(x);
21 }
22
23 // Sort the y-coordinates for each x in xToYCoordinates
24 for (auto& entry : xToYCoordinates) {
25 sort(entry.second.begin(), entry.second.end());
26 }
27
28 // Sort the x-coordinates for each y in yToXCoordinates
29 for (auto& entry : yToXCoordinates) {
30 sort(entry.second.begin(), entry.second.end());
31 }
32
33 int coveredBuildingCount = 0;
34
35 // Check each building to see if it's covered by others
36 for (const auto& building : buildings) {
37 int x = building[0];
38 int y = building[1];
39
40 const vector<int>& yCoordinatesAtX = xToYCoordinates[x];
41 const vector<int>& xCoordinatesAtY = yToXCoordinates[y];
42
43 // Check if there's at least one building on both sides (x and y) that covers the current building
44 if (xCoordinatesAtY.front() < x && x < xCoordinatesAtY.back() &&
45 yCoordinatesAtX.front() < y && y < yCoordinatesAtX.back()) {
46 coveredBuildingCount++;
47 }
48 }
49
50 return coveredBuildingCount;
51 }
52};
53
1function countCoveredBuildings(n: number, buildings: number[][]): number {
2 // Maps to hold x and y coordinates grouped separately
3 const xMap: Map<number, number[]> = new Map();
4 const yMap: Map<number, number[]> = new Map();
5
6 // Populate the maps with building coordinates
7 for (const [x, y] of buildings) {
8 // Group buildings by their x coordinate
9 if (!xMap.has(x)) xMap.set(x, []);
10 xMap.get(x)?.push(y);
11
12 // Group buildings by their y coordinate
13 if (!yMap.has(y)) yMap.set(y, []);
14 yMap.get(y)?.push(x);
15 }
16
17 // Sort the y values for each x in ascending order
18 for (const yList of xMap.values()) {
19 yList.sort((a, b) => a - b);
20 }
21
22 // Sort the x values for each y in ascending order
23 for (const xList of yMap.values()) {
24 xList.sort((a, b) => a - b);
25 }
26
27 let coveredCount = 0;
28
29 // Iterate through each building to check if it's covered
30 for (const [x, y] of buildings) {
31 const yListForX = xMap.get(x)!; // List of y coordinates for the current x
32 const xListForY = yMap.get(y)!; // List of x coordinates for the current y
33
34 // Check if there exist buildings both to the left and right (in x) and above and below (in y)
35 if (
36 xListForY[0] < x && x < xListForY[xListForY.length - 1] &&
37 yListForX[0] < y && y < yListForX[yListForX.length - 1]
38 ) {
39 coveredCount++; // Increase count if the current building is covered
40 }
41 }
42
43 return coveredCount;
44}
45
Time and Space Complexity
The given code iterates through the buildings
list to populate two dictionaries g1
and g2
, where each dictionary maintains a list of coordinates. The iteration is done using a for loop, which makes the time complexity for this operation , where n
is the number of buildings.
Sorting each list in g1
and g2
individually takes time for each list, where k
is the length of that list. In the worst case, each list can contain up to n
elements, therefore, the total sorting operation contributes to the time complexity.
Finally, the code iterates over the buildings
list once more to check the conditions for counting covered buildings, which is an operation.
Therefore, the overall time complexity is due to the sorting step.
The space complexity of the code is . This is due to the dictionaries g1
and g2
, which store up to n
elements collectively where each building's coordinates are used to fill in these lists.
Learn more about how to find time and space complexity quickly.
Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?
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!