2201. Count Artifacts That Can Be Extracted

MediumArrayHash TableSimulation
Leetcode Link

Problem Description

In the given LeetCode problem, we have a grid of n x n size where some artifacts are buried. The grid is such that each cell can be represented by coordinates (r, c), where r is the row number and c is the column number, with both being 0-indexed. Artifacts are buried in rectangular areas within this grid. Each artifact is defined by two coordinates - the top-left cell (r1, c1) and the bottom-right cell (r2, c2). This forms a rectangle in the grid, including the cells on the border of these coordinates.

As part of the excavation process, we dig certain cells in the grid, specified by array dig. Each element in dig is a pair of coordinates (r, c) indicating a cell we have dug up. An artifact is considered to be extracted if all the cells covering it are excavated. The goal is to find out how many artifacts can be completely extracted given the cells we have dug.

Intuition

To solve this problem, the first step is to keep track of all the dug cells in an efficient manner. Since we need to check whether a cell is dug or not, we can use a set data structure (s). This set will allow us to quickly determine the presence or absence of each dug cell by using the coordinates as a unique key for each cell.

Once we have this set of dug cells, the next step is to go through each artifact described in the artifacts array. For each artifact, we iterate over every cell that it covers, which are the cells bounded by (r1, c1) and (r2, c2) inclusive. We check if all these cells are present in our set of dug cells.

The check function accomplishes this iteration and checking. If at least one cell belonging to an artifact is not present in the dug cells set, it means that the artifact cannot be fully extracted, and thus, we return False. If all of the cells are present, we return True indicating the artifact can be extracted.

Finally, we use a generator expression to sum up the number of True values returned by the check function for each artifact, which translates to the number of artifacts that can be extracted.

Not Sure What to Study? Take the 2-min Quiz to Find Your Missing Piece:

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

Solution Approach

The solution follows a simple approach mainly utilizing set data structures and iteration.

  1. First, we convert the dig list into a set s which includes tuples of coordinates (i, j) for each cell we dig. The set data structure is chosen for its efficient O(1) average time complexity for lookup operations, which is crucial since we need to check if each cell of an artifact has been dug.

  2. A helper function check is defined which takes an artifact as an argument, represented by a list [r1, c1, r2, c2]. The function checks if all cells that belong to the artifact have been dug. It does this by iterating over all cells in the rectangular area that the artifact covers—from r1 to r2 and c1 to c2—and checking if each cell (x, y) is in the set s. If any cell is not present, check returns False because this means the artifact is incomplete and cannot be extracted.

  3. If all cells of the artifact are found in the set, check returns True.

  4. The main function digArtifacts applies the check function to each artifact in the artifacts array and sums up the True values (logically equivalent to 1) to obtain the total number of complete artifacts that can be extracted. This uses Python's sum function with a generator expression, which is a compact and efficient way to sum a sequence of boolean values.

Here is the relevant section of the code demonstrating the process:

1s = {(i, j) for i, j in dig}  # Convert the dig list to a set for efficient lookups
2def check(artifact):
3    r1, c1, r2, c2 = artifact  # Unpack the coordinates of the artifact
4    for x in range(r1, r2 + 1):  # Iterate over the rows
5        for y in range(c1, c2 + 1):  # Iterate over the columns
6            if (x, y) not in s:  # Check if the cell is not dug
7                return False
8    return True  # All cells are dug and artifact can be extracted
9
10# Sum up the instances where the artifact can be extracted
11return sum(check(v) for v in artifacts)

Each step of the approach is designed to cut down on unnecessary computations, which is why we use a set for digs and the check function only iterates over cells belonging to the artifacts and not the entire grid. This solution has a time complexity that scales with the number of digs and the size of the artifacts, as opposed to the entire grid size, making it efficient even for larger grid sizes.

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

Which algorithm should you use to find a node that is close to the root of the tree?

Example Walkthrough

Let's consider the following example to illustrate the solution approach:

Suppose we have a grid of size 3 x 3 and there are two artifacts buried:

  • Artifact A with coordinates [0, 0, 1, 1], which spans from the top-left corner to the cell in the second row and second column.
  • Artifact B with coordinates [1, 1, 2, 2], which spans from the cell in the second row and second column to the bottom-right corner.

The dig array contains the cells that have been dug: [(0, 0), (0, 1), (1, 1), (2, 2)].

To determine how many artifacts can be completely extracted, we will follow the solution approach:

  1. Convert the dig array into a set for efficient lookups.

    s = {(0, 0), (0, 1), (1, 1), (2, 2)}

  2. Use the check function to verify whether all the cells of an artifact have been dug.

  3. For Artifact A, the check function will look at coordinates:

    • (0, 0), which is in the set s
    • (0, 1), which is in the set s
    • (1, 0), which is not in the set s
    • (1, 1), which is in the set s

    Since the cell (1, 0) hasn't been dug, Artifact A cannot be fully extracted; hence, check returns False.

  4. For Artifact B, the check function will verify the following cells:

    • (1, 1), which is in the set s
    • (1, 2), which is not in the set s
    • (2, 1), which is not in the set s
    • (2, 2), which is in the set s

    Since the cells (1, 2) and (2, 1) haven't been dug, Artifact B cannot be fully extracted; thus, check returns False.

  5. Now, we sum up the True values returned by the check function for each artifact. Since both artifacts returned False, the sum is 0, meaning no artifacts can be completely extracted based on the cells dug.

By using this solution approach, we are focusing the computation only on the cells that need to be checked, avoiding unnecessary work on other grid cells that do not contain artifacts. This example illustrates how combining set data structures with a focused checking function works efficiently to solve the problem.

Solution Implementation

1from typing import List
2
3class Solution:
4    def digArtifacts(self, n: int, artifacts: List[List[int]], digs: List[List[int]]) -> int:
5        # Helper function to check if all parts of the artifact have been dug up.
6        def is_artifact_fully_dug(artifact):
7            # Unpack the corners of the rectangular artifact.
8            top_left_row, top_left_col, bottom_right_row, bottom_right_col = artifact
9          
10            # Iterate through all positions of the artifact.
11            for row in range(top_left_row, bottom_right_row + 1):
12                for col in range(top_left_col, bottom_right_col + 1):
13                    # Check if the current position has not been dug.
14                    if (row, col) not in dug_positions_set:
15                        return False  # If any position is undug, artifact is incomplete.
16            return True  # All positions dug, artifact is complete.
17
18        # Create a set of dug positions for faster lookup.
19        dug_positions_set = {(row, col) for row, col in digs}
20      
21        # Count the number of fully dug artifacts using the helper function.
22        fully_dug_artifacts_count = sum(is_artifact_fully_dug(artifact) for artifact in artifacts)
23      
24        return fully_dug_artifacts_count
25
26# Example usage:
27# sol = Solution()
28# result = sol.digArtifacts(n=4, artifacts=[[1,0,2,0],[0,2,1,3]], digs=[[0,3],[1,0],[2,1]])
29# print(result)  # Expected output: 1, since only the second artifact is fully dug up.
30
1class Solution {
2
3    public int digArtifacts(int n, int[][] artifacts, int[][] dig) {
4        // Create a set to store the positions that have been dug.
5        Set<Integer> dugPositions = new HashSet<>();
6        // Convert the 2D dig positions into a 1D format and add to the set.
7        for (int[] position : dig) {
8            dugPositions.add(position[0] * n + position[1]);
9        }
10        int numFullyDugArtifacts = 0; // Counter for fully excavated artifacts.
11      
12        // Check each artifact to see if all its parts have been dug up.
13        for (int[] artifact : artifacts) {
14            if (isArtifactFullyDug(artifact, dugPositions, n)) {
15                numFullyDugArtifacts++; // Increment the counter if an artifact is fully dug.
16            }
17        }
18      
19        return numFullyDugArtifacts;
20    }
21
22    // Helper method to check if all the cells of an artifact have been dug.
23    private boolean isArtifactFullyDug(int[] artifact, Set<Integer> dugPositions, int n) {
24        int topRow = artifact[0], leftColumn = artifact[1], bottomRow = artifact[2], rightColumn = artifact[3];
25        // Iterate over the cells that the artifact spans.
26        for (int i = topRow; i <= bottomRow; ++i) {
27            for (int j = leftColumn; j <= rightColumn; ++j) {
28                // If a cell has not been dug, the artifact is not fully excavated.
29                if (!dugPositions.contains(i * n + j)) {
30                    return false;
31                }
32            }
33        }
34        // If all cells have been dug, the artifact is fully excavated.
35        return true;
36    }
37}
38
1class Solution {
2public:
3    // Function to compute the number of fully dug artifacts
4    int digArtifacts(int gridSize, vector<vector<int>>& artifacts, vector<vector<int>>& digs) {
5        // Create a hash set to store the dig positions for constant time lookup
6        unordered_set<int> dugPositions;
7        for (auto& dig : digs) {
8            // Convert 2D coordinates to a single integer using the grid size
9            dugPositions.insert(dig[0] * gridSize + dig[1]);
10        }
11
12        // Variable to store the count of fully dug artifacts
13        int fullyDugArtifacts = 0;
14
15        // Check each artifact
16        for (auto& artifact : artifacts) {
17            // Increment the fully dug artifacts count if the artifact is fully dug
18            if (isArtifactFullyDug(artifact, dugPositions, gridSize)) {
19                ++fullyDugArtifacts;
20            }
21        }
22
23        // Return the total count of fully dug artifacts
24        return fullyDugArtifacts;
25    }
26
27private:
28    // Helper function to check if all parts of the artifact have been dug up
29    bool isArtifactFullyDug(vector<int>& artifact, unordered_set<int>& dugPositions, int gridSize) {
30        // Artifact's top-left and bottom-right coordinates
31        int rowStart = artifact[0];
32        int colStart = artifact[1];
33        int rowEnd = artifact[2];
34        int colEnd = artifact[3];
35
36        // Iterate over the grid cells covered by the artifact
37        for (int i = rowStart; i <= rowEnd; ++i) {
38            for (int j = colStart; j <= colEnd; ++j) {
39                // Convert 2D coordinates to a single integer using the grid size
40                int position = i * gridSize + j;
41
42                // If any part of the artifact is not dug, return false
43                if (!dugPositions.count(position)) {
44                    return false;
45                }
46            }
47        }
48      
49        // If all parts are dug, the artifact is fully dug, return true
50        return true;
51    }
52};
53
1function digArtifacts(n: number, artifacts: number[][], dig: number[][]): number {
2    // Create a 2D array to track visited cells with initial value `false`
3    let visitedCells = Array.from({ length: n }, () => new Array(n).fill(false));
4  
5    // Mark the cells that have been dug as visited
6    for (let [row, col] of dig) {
7        visitedCells[row][col] = true;
8    }
9  
10    // Initialize a counter for completely uncovered artifacts
11    let uncoveredArtifactsCount = 0;
12  
13    // Check each artifact to see if it is fully uncovered
14    for (let [startRow, startCol, endRow, endCol] of artifacts) {
15        let isUncovered = true;
16      
17        // Iterate over the cells covered by the artifact
18        for (let i = startRow; i <= endRow && isUncovered; i++) {
19            for (let j = startCol; j <= endCol && isUncovered; j++) {
20                // If any cell of the artifact is not visited, mark the artifact as not uncovered
21                if (!visitedCells[i][j]) {
22                    isUncovered = false;
23                }
24            }
25        }
26      
27        // If the artifact is completely uncovered, increment the count
28        if (isUncovered) {
29            uncoveredArtifactsCount++;
30        }
31    }
32  
33    // Return the total count of completely uncovered artifacts
34    return uncoveredArtifactsCount;
35}
36
Not Sure What to Study? Take the 2-min Quiz:

Which of the following shows the order of node visit in a Breadth-first Search?

Time and Space Complexity

Time Complexity

The time complexity of the given code can be analyzed in the following steps:

  1. Conversion of dig list into a set s: This operation iterates over each element in the dig list once. Therefore, the time complexity for this part is O(D), where D is the length of the dig list.

  2. The check function: This function is called for each artifact. Inside the function, there's a nested loop that iterates over the artifact's area which is at most (r2 - r1 + 1) * (c2 - c1 + 1) for each artifact. In the worst case, this could be equal to the size of the grid (n * n), if the artifact covers the entire grid.

  3. The last line sums up the results of the check function for each artifact. Since check is invoked once per artifact, the time complexity for calling check overall is O(A * n^2), where A is the number of artifacts, since we consider the worst case where an artifact spans the entire grid.

Combining these complexities, we have an overall time complexity of O(D + A * n^2).

Space Complexity

The space complexity can be analyzed by looking at the additional space used by the program components:

  1. The set s that stores the dug positions: In the worst case, it will store all D positions where digs happened, yielding a space complexity of O(D).

  2. The space used for the check function's call stack is negligible as it does not involve recursive calls or additional data structures with size depending on the inputs. However, due to the iterating for each cell in the range of an artifact, at worst case the space required to store the temporary variables x and y in the stack is constant, O(1).

Overall, since there are no other significant data structures being used, the space complexity is O(D).

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

Fast Track Your Learning with Our Quick Skills Quiz:

Which algorithm should you use to find a node that is close to the root of the tree?


Recommended Readings


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫