2201. Count Artifacts That Can Be Extracted
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.
Solution Approach
The solution follows a simple approach mainly utilizing set data structures and iteration.
-
First, we convert the
dig
list into a sets
which includes tuples of coordinates(i, j)
for each cell we dig. The set data structure is chosen for its efficientO(1)
average time complexity for lookup operations, which is crucial since we need to check if each cell of an artifact has been dug. -
A helper function
check
is defined which takes anartifact
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—fromr1
tor2
andc1
toc2
—and checking if each cell(x, y)
is in the sets
. If any cell is not present,check
returnsFalse
because this means the artifact is incomplete and cannot be extracted. -
If all cells of the artifact are found in the set,
check
returnsTrue
. -
The main function
digArtifacts
applies thecheck
function to each artifact in theartifacts
array and sums up theTrue
values (logically equivalent to1
) to obtain the total number of complete artifacts that can be extracted. This uses Python'ssum
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:
s = {(i, j) for i, j in dig} # Convert the dig list to a set for efficient lookups
def check(artifact):
r1, c1, r2, c2 = artifact # Unpack the coordinates of the artifact
for x in range(r1, r2 + 1): # Iterate over the rows
for y in range(c1, c2 + 1): # Iterate over the columns
if (x, y) not in s: # Check if the cell is not dug
return False
return True # All cells are dug and artifact can be extracted
# Sum up the instances where the artifact can be extracted
return 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.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample 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:
-
Convert the dig array into a set for efficient lookups.
s = {(0, 0), (0, 1), (1, 1), (2, 2)}
-
Use the
check
function to verify whether all the cells of an artifact have been dug. -
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
returnsFalse
. - (0, 0), which is in the set
-
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
returnsFalse
. - (1, 1), which is in the set
-
Now, we sum up the
True
values returned by thecheck
function for each artifact. Since both artifacts returnedFalse
, the sum is0
, 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
Time and Space Complexity
Time Complexity
The time complexity of the given code can be analyzed in the following steps:
-
Conversion of
dig
list into a sets
: This operation iterates over each element in thedig
list once. Therefore, the time complexity for this part isO(D)
, whereD
is the length of thedig
list. -
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. -
The last line sums up the results of the
check
function for each artifact. Sincecheck
is invoked once per artifact, the time complexity for callingcheck
overall isO(A * n^2)
, whereA
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:
-
The set
s
that stores the dug positions: In the worst case, it will store allD
positions where digs happened, yielding a space complexity ofO(D)
. -
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 variablesx
andy
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.
What does the following code do?
1def f(arr1, arr2):
2 i, j = 0, 0
3 new_arr = []
4 while i < len(arr1) and j < len(arr2):
5 if arr1[i] < arr2[j]:
6 new_arr.append(arr1[i])
7 i += 1
8 else:
9 new_arr.append(arr2[j])
10 j += 1
11 new_arr.extend(arr1[i:])
12 new_arr.extend(arr2[j:])
13 return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2 int i = 0, j = 0;
3 List<Integer> newArr = new ArrayList<>();
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.add(arr1[i]);
8 i++;
9 } else {
10 newArr.add(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.add(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.add(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
1function f(arr1, arr2) {
2 let i = 0, j = 0;
3 let newArr = [];
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.push(arr1[i]);
8 i++;
9 } else {
10 newArr.push(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.push(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.push(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
Recommended Readings
LeetCode 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 we
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!