Count Artifacts That Can Be Extracted
MedPrompt
There is an n x n 0-indexed grid with some artifacts buried in it. You are given the integer n and a 0-indexed 2D integer array artifacts describing the positions of the rectangular artifacts where artifacts[i] = [r1i, c1i, r2i, c2i] denotes that the ith artifact is buried in the subgrid where:
(r1i, c1i)is the coordinate of the top-left cell of theithartifact and(r2i, c2i)is the coordinate of the bottom-right cell of theithartifact.
You will excavate some cells of the grid and remove all the mud from them. If the cell has a part of an artifact buried underneath, it will be uncovered. If all the parts of an artifact are uncovered, you can extract it.
Given a 0-indexed 2D integer array dig where dig[i] = [ri, ci] indicates that you will excavate the cell (ri, ci), return the number of artifacts that you can extract.
The test cases are generated such that:
- No two artifacts overlap.
- Each artifact only covers at most
4cells. - The entries of
digare unique.
Example 1:
Input: n = 2, artifacts = [[0,0,0,0],[0,1,1,1]], dig = [[0,0],[0,1]]
Output: 1
Explanation:
The different colors represent different artifacts. Excavated cells are labeled with a 'D' in the grid.
There is 1 artifact that can be extracted, namely the red artifact.
The blue artifact has one part in cell (1,1) which remains uncovered, so we cannot extract it.
Thus, we return 1.Example 2:
Input: n = 2, artifacts = [[0,0,0,0],[0,1,1,1]], dig = [[0,0],[0,1],[1,1]]
Output: 2
Explanation: Both the red and blue artifacts have all parts uncovered (labeled with a 'D') and can be extracted, so we return 2.
Constraints:
1 <= n <= 10001 <= artifacts.length, dig.length <= min(n2, 105)artifacts[i].length == 4dig[i].length == 20 <= r1i, c1i, r2i, c2i, ri, ci <= n - 1r1i <= r2ic1i <= c2i- No two artifacts will overlap.
- The number of cells covered by an artifact is at most
4. - The entries of
digare unique.
Approaches
4 approaches with complexity analysis and trade-offs.
A significant improvement over brute force is to avoid repeatedly scanning the dig array. We can use a 2D grid, the same size as the excavation area, to mark which cells have been dug. We first iterate through the dig array and mark the corresponding cells in our grid. Then, for each artifact, we check this grid to see if all its cells are marked as dug.
Algorithm
- Create a boolean 2D array
dugGridof sizen x n, initialized tofalse. - Iterate through each cell
[r, c]indigand setdugGrid[r][c] = true. - Initialize
extractedCount = 0. - For each
artifactinartifacts:- Assume the artifact is extractable (
isExtractable = true). - Iterate through all cells of the artifact.
- For each cell, check if it's marked as true in
dugGrid. - If any cell is not marked, set
isExtractable = falseand break.
- Assume the artifact is extractable (
- If
isExtractableis still true after checking all its cells, incrementextractedCount. - Return
extractedCount.
Walkthrough
This method pre-processes the dug locations. By creating an n x n boolean grid, we can mark all excavated cells in O(D) time, where D is the number of dug cells. After this one-time setup, checking if a cell is dug becomes an O(1) operation. We then iterate through each artifact and check its constituent cells against this grid. The main drawback is the space and time required to handle the grid itself, which depends on n^2.
class Solution { public int countArtifacts(int n, int[][] artifacts, int[][] dig) { boolean[][] dugGrid = new boolean[n][n]; for (int[] d : dig) { dugGrid[d[0]][d[1]] = true; } int extractedCount = 0; for (int[] artifact : artifacts) { int r1 = artifact[0], c1 = artifact[1], r2 = artifact[2], c2 = artifact[3]; boolean isExtractable = true; for (int r = r1; r <= r2; r++) { for (int c = c1; c <= c2; c++) { if (!dugGrid[r][c]) { isExtractable = false; break; } } if (!isExtractable) { break; } } if (isExtractable) { extractedCount++; } } return extractedCount; }}Complexity
Time
O(n^2 + D + A), where `n` is the grid dimension, `D` is `dig.length`, and `A` is `artifacts.length`. The `n^2` term comes from initializing the grid.
Space
O(n^2) to store the `dugGrid`.
Trade-offs
Pros
Much faster than the brute-force approach.
Conceptually simple, mapping directly to the grid-based nature of the problem.
Cons
High space complexity, O(n^2), which can be large.
Time complexity includes an O(n^2) term for initialization, which can be the bottleneck if n is large.
This approach directly translates the problem statement into code. We iterate through each artifact. For each artifact, we determine all the grid cells it occupies. Then, for each of these cells, we scan the entire dig array to see if the cell has been excavated. If all cells of an artifact are found in the dig array, we count it as extractable.
Algorithm
- Initialize a counter for extracted artifacts to zero.
- Iterate through each artifact in the
artifactslist. - For each artifact, generate a list of all cells it occupies.
- For each cell of the artifact, perform a linear scan through the entire
digarray to check if it has been excavated. - If a cell is not found in
dig, the artifact cannot be extracted; stop checking this artifact and move to the next. - If all cells of an artifact are found in
dig, increment the counter. - Return the final count.
Walkthrough
The brute-force method checks every artifact one by one. For an artifact defined by [r1, c1, r2, c2], it iterates through all cells (r, c) where r1 <= r <= r2 and c1 <= c <= c2. For each such cell, it iterates through the dig array to find a match. This nested iteration makes the approach very slow.
class Solution { public int countArtifacts(int n, int[][] artifacts, int[][] dig) { int extractedCount = 0; for (int[] artifact : artifacts) { int r1 = artifact[0], c1 = artifact[1], r2 = artifact[2], c2 = artifact[3]; boolean allDug = true; // Iterate over all cells of the artifact for (int r = r1; r <= r2; r++) { for (int c = c1; c <= c2; c++) { boolean found = false; // Search for the cell in the dig array for (int[] digCell : dig) { if (digCell[0] == r && digCell[1] == c) { found = true; break; } } if (!found) { allDug = false; break; } } if (!allDug) { break; } } if (allDug) { extractedCount++; } } return extractedCount; }}Complexity
Time
O(A * S * D), where A is `artifacts.length`, S is the number of cells per artifact (at most 4), and D is `dig.length`. This simplifies to O(A * D).
Space
O(1) extra space.
Trade-offs
Pros
Simple to understand and implement.
Requires no extra space.
Cons
Extremely inefficient due to nested loops.
Will result in a 'Time Limit Exceeded' error for large inputs.
This approach improves upon the grid marking method by reducing space complexity when the number of dug cells D is much smaller than n^2. Instead of a full 2D grid, we use a hash set to store the coordinates of the dug cells. This allows for average O(1) time complexity for checking if a cell has been excavated.
Algorithm
- Create a
HashSetto store the coordinates of dug cells. - To store a 2D coordinate
(r, c), encode it into a single integer, e.g.,r * n + c. - Iterate through the
digarray and add the encoded coordinate of each cell to the hash set. - Initialize
extractedCount = 0. - For each
artifact:- Assume it's extractable (
isExtractable = true). - Iterate through all cells of the artifact.
- For each cell, check if its encoded coordinate exists in the hash set.
- If not found, set
isExtractable = falseand break.
- Assume it's extractable (
- If
isExtractableremains true, incrementextractedCount. - Return
extractedCount.
Walkthrough
By using a hash set, we only store the locations that have been dug, which is more memory-efficient than an n x n grid if D is significantly smaller than n^2. The process involves two main stages: first, populating the hash set with all dug cells, and second, iterating through each artifact to verify if all its parts are in the set. The check for each part of an artifact is an average O(1) lookup in the hash set.
import java.util.HashSet;import java.util.Set; class Solution { public int countArtifacts(int n, int[][] artifacts, int[][] dig) { Set<Integer> dugCells = new HashSet<>(); for (int[] d : dig) { dugCells.add(d[0] * n + d[1]); } int extractedCount = 0; for (int[] artifact : artifacts) { int r1 = artifact[0], c1 = artifact[1], r2 = artifact[2], c2 = artifact[3]; boolean isExtractable = true; for (int r = r1; r <= r2; r++) { for (int c = c1; c <= c2; c++) { if (!dugCells.contains(r * n + c)) { isExtractable = false; break; } } if (!isExtractable) { break; } } if (isExtractable) { extractedCount++; } } return extractedCount; }}Complexity
Time
O(D + A), where D is `dig.length` and A is `artifacts.length`. This is because populating the set takes O(D) and checking all artifacts takes O(A) as each artifact has at most 4 cells.
Space
O(D), where D is the number of dug cells, to store them in the hash set.
Trade-offs
Pros
Optimal time complexity.
Space-efficient when the number of dug cells is much smaller than n*n.
Cons
Uses O(D) space, which could be large if there are many dug cells.
Hashing can have a small constant-time overhead compared to direct array access.
This is another highly efficient approach with the same time complexity as the hash set method but potentially different space usage. Instead of checking from artifacts to dug sites, we can go the other way. We first pre-process all artifacts to know which cells belong to which artifact and the total size of each artifact. Then, we iterate through the dug sites, and for each dug cell, we credit the corresponding artifact. Finally, we count how many artifacts have all their cells credited (i.e., dug).
Algorithm
- Create a map
cellToArtifactto link cell coordinates to artifact indices and an arrayartifactSizesto store the size of each artifact. - Pre-process by iterating through all artifacts. For each artifact, calculate its size and populate the map with its cells.
- Create an array
uncoveredPartsinitialized to zeros to count uncovered cells for each artifact. - Iterate through the
digarray. For each dug cell, look up which artifact it belongs to using the map and increment theuncoveredPartscount for that artifact. - Finally, iterate through the artifacts. If
uncoveredParts[i]equalsartifactSizes[i], it means the artifact is fully excavated. Increment a result counter. - Return the result counter.
Walkthrough
This method changes the perspective: instead of asking "is this artifact cell dug?", it asks "which artifact does this dug cell uncover?". It requires pre-computation to build a mapping from each cell covered by an artifact to the artifact's identifier (its index). It also tracks the total number of cells for each artifact. After processing all dug sites and updating the counts of uncovered parts for each artifact, a final pass determines which artifacts are complete.
import java.util.HashMap;import java.util.Map; class Solution { public int countArtifacts(int n, int[][] artifacts, int[][] dig) { int numArtifacts = artifacts.length; int[] artifactSizes = new int[numArtifacts]; int[] uncoveredParts = new int[numArtifacts]; Map<Integer, Integer> cellToArtifact = new HashMap<>(); for (int i = 0; i < numArtifacts; i++) { int[] artifact = artifacts[i]; int r1 = artifact[0], c1 = artifact[1], r2 = artifact[2], c2 = artifact[3]; int size = 0; for (int r = r1; r <= r2; r++) { for (int c = c1; c <= c2; c++) { cellToArtifact.put(r * n + c, i); size++; } } artifactSizes[i] = size; } for (int[] d : dig) { int cellKey = d[0] * n + d[1]; Integer artifactIndex = cellToArtifact.get(cellKey); if (artifactIndex != null) { uncoveredParts[artifactIndex]++; } } int extractedCount = 0; for (int i = 0; i < numArtifacts; i++) { if (artifactSizes[i] > 0 && uncoveredParts[i] == artifactSizes[i]) { extractedCount++; } } return extractedCount; }}Complexity
Time
O(A + D). O(A) to pre-process artifacts (since each has at most 4 cells), O(D) to process digs, and O(A) for the final count.
Space
O(A), as the total number of cells covered by artifacts is at most 4*A. The map and arrays scale with the number of artifacts.
Trade-offs
Pros
Optimal time complexity.
Space complexity depends on the total number of cells covered by artifacts, which can be more efficient than the HashSet approach if
4*Ais smaller thanD.
Cons
Slightly more complex implementation with multiple data structures.
Requires multiple passes over the data (once for artifacts, once for digs, once for final count).
Solutions
Solution
class Solution {private Set<Integer> s = new HashSet<>();private int n;public int digArtifacts(int n, int[][] artifacts, int[][] dig) { this.n = n; for (var p : dig) { s.add(p[0] * n + p[1]); } int ans = 0; for (var a : artifacts) { ans += check(a); } return ans; }private int check(int[] a) { int x1 = a[0], y1 = a[1], x2 = a[2], y2 = a[3]; for (int x = x1; x <= x2; ++x) { for (int y = y1; y <= y2; ++y) { if (!s.contains(x * n + y)) { return 0; } } } return 1; }}class Solution {public: int digArtifacts(int n, vector<vector<int>> &artifacts, vector<vector<int>> &dig) { unordered_set<int> s; for (auto &p : dig) { s.insert(p[0] * n + p[1]); } auto check = [&](vector<int> &a) { int x1 = a[0], y1 = a[1], x2 = a[2], y2 = a[3]; for (int x = x1; x <= x2; ++x) { for (int y = y1; y <= y2; ++y) { if (!s.count(x * n + y)) { return 0; } } } return 1; }; int ans = 0; for (auto &a : artifacts) { ans += check(a); } return ans; }};class Solution: def digArtifacts(self, n: int, artifacts: List[List[int]], dig: List[List[int]]) -> int: def check(a: List[int]) -> bool: x1, y1, x2, y2 = a return all((x, y) in s for x in range(x1, x2 + 1) for y in range(y1, y2 + 1)) s = {(i, j) for i, j in dig} return sum(check(a) for a in artifacts)Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.