Facebook Pixel

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.

  1. Hash Tables for Categorization: We use two hash tables, g1 and g2. g1[x] stores all the y-coordinates of buildings with the same x-coordinate x. Similarly, g2[y] keeps all the x-coordinates of buildings sharing the y-coordinate y. This sets the groundwork for categorizing and accessing buildings based on their respective axes.

  2. 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.

  3. 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] where l2 is the sorted list of x-coordinates at y.
    • l1[0] < y < l1[-1] where l1 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:

  1. Data Structures Setup:

    • We initialize two hash tables, g1 and g2, using Python's defaultdict from the collections module. These will help in grouping buildings by their coordinates.
    • g1[x] will hold a list of all y-coordinates for buildings with x-coordinate x.
    • g2[y] will contain a list of all x-coordinates for buildings sharing the y-coordinate y.
  2. Populating Hash Tables:

    • Iterate through each building's coordinates (x, y) in the given buildings list.
    • For each building, append the y-coordinate y to g1[x] and the x-coordinate x to g2[y].
  3. Sorting for Quick Access:

    • After populating the hash tables, sort the lists in both g1 and g2. This enables quick determination of neighboring buildings along the same line of coordinate.
  4. 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 from g1[x], which contains y-coordinates, and l2 from g2[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.
  5. Return the Result:

    • Once all buildings are processed, the final count stored in ans is returned as the result.

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 Evaluator

Example 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 and g2.

Step 2: Populating Hash Tables

  • For building (1, 1), g1[1] = [1] and g2[1] = [1].
  • For building (1, 2), g1[1] = [1, 2] and g2[2] = [1].
  • For building (2, 2), g1[2] = [2] and g2[2] = [1, 2].

Step 3: Sorting for Quick Access

  • Sort the coordinates in g1 and g2:
    • 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] from g1[1] and l2 = [1] from g2[1].
    • Condition l1[0] < y < l1[-1] fails as 1 < 1 < 2 is false.
    • Building (1, 1) is not covered.
  • Check building (1, 2):

    • l1 = [1, 2] from g1[1] and l2 = [1, 2] from g2[2].
    • Condition l1[0] < y < l1[-1] fails as 1 < 2 < 2 is false.
    • Building (1, 2) is not covered.
  • Check building (2, 2):

    • l1 = [2] from g1[2] and l2 = [1, 2] from g2[2].
    • Condition l2[0] < x < l2[-1] fails as 1 < 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 O(n)O(n), where n is the number of buildings.

Sorting each list in g1 and g2 individually takes O(kβ‹…log⁑k)O(k \cdot \log k) 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 O(nβ‹…log⁑n)O(n \cdot \log n) 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 O(n)O(n) operation.

Therefore, the overall time complexity is O(nΓ—log⁑n)O(n \times \log n) due to the sorting step.

The space complexity of the code is O(n)O(n). 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.


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

Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?


Recommended Readings

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

Load More