2132. Stamping the Grid


Problem Description

The problem presents a grid consisting of cells marked either 0 (empty) or 1 (occupied). The challenge is to cover all the empty cells with stamps of a pre-defined size (stampHeight x stampWidth) without overlapping any occupied cells. Stamps can overlap each other, they cannot be rotated, and they must fit entirely within the grid boundaries.

Intuition

To solve this problem, we first need to quickly determine whether a stamp can be placed onto a certain position on the grid. The key is to check that all cells under the stamp are empty. To do this efficiently, we employ a "prefix sum matrix." A prefix sum allows us to calculate the sum of any sub-matrix in constant time.

The prefix sum is constructed such that each cell in this auxiliary matrix contains the sum of all cells above and to the left, including the current cell in the original grid. With this prefix sum matrix, we can quickly determine the sum of any sub-matrix by subtracting the appropriate prefix sums.

Now, when placing a stamp on the grid, we mark the cells in the difference matrix that corresponds to the stamp's area. A difference matrix allows us to record changes to a range within the matrix in constant time. By incrementing the top-left corner of the stamp area and decrementing the point just outside the bottom-right corner of the proposed stamp area, we define a range that can receive a stamp.

Next, we build another prefix sum from the difference matrix. This new matrix helps us understand how many stamps cover each cell. As we iterate through the entire grid, we check each empty cell to see if it has been covered by at least one stamp. If we find any empty cell not covered by a stamp, we know the task is impossible, and we return false.

If all empty cells are covered, we've met the requirements and can return true. Each step of the solution builds on a logical progression from determining single-cell coverage to ensuring full-grid compliance with the stamp placement rules.

Learn more about Greedy and Prefix Sum patterns.

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

How would you design a stack which has a function min that returns the minimum element in the stack, in addition to push and pop? All push, pop, min should have running time O(1).

Solution Approach

The provided solution can be broken down into the following steps, utilizing concepts such as prefix sums and difference matrices:

  1. Constructing the Prefix Sum Matrix (s): The prefix sum matrix is built so that each cell represents the sum of all cells above and to the left in the grid, including the cell itself. This is calculated by s[i + 1][j + 1] = s[i + 1][j] + s[i][j + 1] - s[i][j] + grid[i][j]. This step is the foundation for efficiently checking whether a stamp can be placed in a certain area.

  2. Stamp Placement Check: For a given cell (i, j) that we are trying to cover with a stamp, we check whether the sub-matrix defined by the bottom-right corner (x, y) — where x equals i + stampHeight and y equals j + stampWidth — is within the bounds and contains only zeroes. This is done by verifying s[x][y] - s[x][j] - s[i][y] + s[i][j] == 0.

  3. Updating the Difference Matrix (d): If the stamp can be placed, we update the difference matrix to reflect this. We increment d[i][j] and decrement d[i][y], d[x][j], and d[x][y] to ensure that the affected range captures the stamp placement.

  4. Applying the Difference Matrix: Another two-dimensional prefix sum is calculated from the difference matrix. For each cell (i, j), cnt[i + 1][j + 1] is updated to the sum of the current cell plus the cells above and to the left. This represents how many stamps cover each specific cell.

  5. Validation Check: As we iterate through each cell in the grid again, we verify if it's empty grid[i][j] == 0. If it's uncovered by any stamp cnt[i + 1][j + 1] == 0, we immediately return false as the requirement is violated.

  6. Returning the Result: If all empty cells are covered without breaking any of the rules (all visited cells pass the validation check), we conclude that it's possible to stamp all empty cells and return true.

By using a prefix sum to enable quick sum calculations of sub-matrices and a difference matrix to handle the range updates, the solution efficiently determines whether it's possible to satisfy the stamp placement conditions for the entire grid.

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

What's the output of running the following function using input 56?

1KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13    def dfs(path, res):
14        if len(path) == len(digits):
15            res.append(''.join(path))
16            return
17
18        next_number = digits[len(path)]
19        for letter in KEYBOARD[next_number]:
20            path.append(letter)
21            dfs(path, res)
22            path.pop()
23
24    res = []
25    dfs([], res)
26    return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2    '2', "abc".toCharArray(),
3    '3', "def".toCharArray(),
4    '4', "ghi".toCharArray(),
5    '5', "jkl".toCharArray(),
6    '6', "mno".toCharArray(),
7    '7', "pqrs".toCharArray(),
8    '8', "tuv".toCharArray(),
9    '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13    List<String> res = new ArrayList<>();
14    dfs(new StringBuilder(), res, digits.toCharArray());
15    return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19    if (path.length() == digits.length) {
20        res.add(path.toString());
21        return;
22    }
23    char next_digit = digits[path.length()];
24    for (char letter : KEYBOARD.get(next_digit)) {
25        path.append(letter);
26        dfs(path, res, digits);
27        path.deleteCharAt(path.length() - 1);
28    }
29}
30
1const KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13    let res = [];
14    dfs(digits, [], res);
15    return res;
16}
17
18function dfs(digits, path, res) {
19    if (path.length === digits.length) {
20        res.push(path.join(''));
21        return;
22    }
23    let next_number = digits.charAt(path.length);
24    for (let letter of KEYBOARD[next_number]) {
25        path.push(letter);
26        dfs(digits, path, res);
27        path.pop();
28    }
29}
30

Example Walkthrough

Let's walk through a small example to illustrate the solution approach using a grid of size 3x4 with a stamp size of 2x2.

Consider the grid:

10 1 0 0
21 0 0 0
30 0 0 0

Let's go through the solution steps on this grid:

  1. Construct Prefix Sum Matrix (s): We start by creating a prefix sum matrix s of size 4x5 (one more in each dimension for easier calculation).

    1To build prefix sum matrix `s`, we follow the rule: s[i + 1][j + 1] = s[i + 1][j] + s[i][j + 1] - s[i][j] + grid[i][j].
    2
    3Starting with `s` all zeros:
    40 0 0 0 0
    50 
    60 
    70
    8
    9We add each cell of the grid, cumulatively:
    100 0 0 0 0
    110 0 1 1 1
    120 1 1 2 2
    130 1 2 3 3
  2. Stamp Placement Check: Let's check if we can place the 2x2 stamp at the top-left corner (0,0).

    We check the sub-matrix given by s[2][2] is zero:

    1s[2][2] - s[0][2] - s[2][0] + s[0][0] = 1 - 0 - 1 + 0 = 0

    Since it's not zero, we cannot place the stamp here because there's an occupied cell (1,0) within the range of the stamp.

  3. Updating the Difference Matrix (d): Now, let's attempt to place a stamp at (2,0). We can confirm it fits by checking the respective prefix sum sub-matrix. Since it fits, we update the difference matrix d:

    1d starts as all zeros:
    20 0 0 0 0
    30 
    40 
    50 
    6
    7We increment `d[2][0]` and decrement the cells just outside the bottom-right of stamp area `d[4][2]`:
    80 0 0 0 0
    90 
    100 1 
    110   -1 
  4. Applying the Difference Matrix: Next, we construct a new prefix sum from the d:

    1We calculate using d's values: 
    20  0  0  0  0
    30  0  0  0  0
    40  1  1  1  1
    50  1  1  1  0

    This matrix now indicates the number of stamps covering each cell.

  5. Validation Check: We go back to our original grid and check each cell:

    • grid[0][0] == 0 and cnt[1][1] == 0 (no stamp covers this cell), so we return false.
  6. Returning the Result: There is no need to proceed because we already determined that it's impossible to cover all empty cells with stamps, as per the previous step's validation check.

Using these steps serves to highlight how the algorithm leverages the prefix sum and difference matrix to efficiently solve the problem. In this example, the given grid configuration and stamp size do not allow us to cover all empty cells without overlapping an occupied cell. Therefore, the final output would be false.

Solution Implementation

1class Solution:
2    def possibleToStamp(self, grid: List[List[int]], stampHeight: int, stampWidth: int) -> bool:
3        # Get the dimensions of the grid
4        rows, cols = len(grid), len(grid[0])
5
6        # Initialize prefix sum matrix
7        prefix_sum = [[0] * (cols + 1) for _ in range(rows + 1)]
8        # Calculate the prefix sum of the grid
9        for i in range(rows):
10            for j in range(cols):
11                prefix_sum[i + 1][j + 1] = (
12                    prefix_sum[i][j + 1] + prefix_sum[i + 1][j]
13                    - prefix_sum[i][j] + grid[i][j]
14                )
15
16        # Initialize difference matrix for stamp placements
17        diff_matrix = [[0] * (cols + 1) for _ in range(rows + 1)]
18        # Determine if a stamp can be placed on an empty area
19        for i in range(rows):
20            for j in range(cols):
21                if grid[i][j] == 0:
22                    row_end, col_end = i + stampHeight, j + stampWidth
23                    # Check if the stamp fits in the current position
24                    if row_end <= rows and col_end <= cols and prefix_sum[row_end][col_end] - prefix_sum[row_end][j] - prefix_sum[i][col_end] + prefix_sum[i][j] == 0:
25                        diff_matrix[i][j] += 1
26                        diff_matrix[i][col_end] -= 1
27                        diff_matrix[row_end][j] -= 1
28                        diff_matrix[row_end][col_end] += 1
29
30        # Create matrix to keep track of covered cells
31        coverage_matrix = [[0] * (cols + 1) for _ in range(rows + 1)]
32        # Apply the difference matrix to calculate the number of stamps covering each cell
33        for i in range(rows):
34            for j in range(cols):
35                coverage_matrix[i + 1][j + 1] = (
36                    coverage_matrix[i + 1][j] + coverage_matrix[i][j + 1]
37                    - coverage_matrix[i][j] + diff_matrix[i][j]
38                )
39                # If a cell is supposed to be stamped but has no stamp coverage, return False
40                if grid[i][j] == 0 and coverage_matrix[i + 1][j + 1] == 0:
41                    return False
42        # If every empty cell is covered appropriately with stamps, return True
43        return True
44
1class Solution {
2    public boolean possibleToStamp(int[][] grid, int stampHeight, int stampWidth) {
3        int numRows = grid.length, numCols = grid[0].length;
4        // Prefix sum array to quickly calculate sum of submatrices.
5        int[][] prefixSum = new int[numRows + 1][numCols + 1];
6      
7        // Build the prefixSum array.
8        for (int row = 0; row < numRows; ++row) {
9            for (int col = 0; col < numCols; ++col) {
10                prefixSum[row + 1][col + 1] = prefixSum[row + 1][col] + prefixSum[row][col + 1] - prefixSum[row][col] + grid[row][col];
11            }
12        }
13      
14        // Difference array to apply range updates (stamping).
15        int[][] diff = new int[numRows + 1][numCols + 1];
16      
17        // Iterate over the entire grid to check where stamps can be placed.
18        for (int row = 0; row < numRows; ++row) {
19            for (int col = 0; col < numCols; ++col) {
20                // If we have an empty cell and can fit a stamp starting at (row, col),
21                // then update the difference array.
22                if (grid[row][col] == 0) {
23                    int endRow = row + stampHeight, endCol = col + stampWidth;
24                    if (endRow <= numRows && endCol <= numCols && 
25                        prefixSum[endRow][endCol] - prefixSum[endRow][col] - prefixSum[row][endCol] + prefixSum[row][col] == 0) {
26                        diff[row][col]++;
27                        diff[row][endCol]--;
28                        diff[endRow][col]--;
29                        diff[endRow][endCol]++;
30                    }
31                }
32            }
33        }
34      
35        // Use a running sum to apply the difference array updates to the grid.
36        int[][] coverCount = new int[numRows + 1][numCols + 1];
37        for (int row = 0; row < numRows; ++row) {
38            for (int col = 0; col < numCols; ++col) {
39                coverCount[row + 1][col + 1] = coverCount[row + 1][col] + coverCount[row][col + 1] - coverCount[row][col] + diff[row][col];
40                // If there is an empty cell that is not covered by a stamp, return false.
41                if (grid[row][col] == 0 && coverCount[row + 1][col + 1] == 0) {
42                    return false;
43                }
44            }
45        }
46      
47        // All empty cells are covered by stamps; return true.
48        return true;
49    }
50}
51
1class Solution {
2public:
3    bool possibleToStamp(vector<vector<int>>& grid, int stampHeight, int stampWidth) {
4        int rows = grid.size(), cols = grid[0].size();
5        // Use an auxiliary matrix to perform prefix sum computations
6        vector<vector<int>> prefixSum(rows + 1, vector<int>(cols + 1));
7        // Calculate the prefix sums for all cells
8        for (int i = 0; i < rows; ++i) {
9            for (int j = 0; j < cols; ++j) {
10                prefixSum[i + 1][j + 1] = prefixSum[i + 1][j] + prefixSum[i][j + 1] - prefixSum[i][j] + grid[i][j];
11            }
12        }
13
14        // Initialize a difference matrix to mark stampable regions
15        vector<vector<int>> diff(rows + 1, vector<int>(cols + 1));
16        for (int i = 0; i < rows; ++i) {
17            for (int j = 0; j < cols; ++j) {
18                // If the current cell is filled, it cannot be stamped, skip it
19                if (grid[i][j]) continue;
20                int x = i + stampHeight, y = j + stampWidth;
21                // Check if it's possible to stamp the area starting at (i, j)
22                if (x <= rows && y <= cols && prefixSum[x][y] - prefixSum[i][y] - prefixSum[x][j] + prefixSum[i][j] == 0) {
23                    // Mark corners of the stamp region in the difference matrix
24                    diff[i][j]++;
25                    diff[x][j]--;
26                    diff[i][y]--;
27                    diff[x][y]++;
28                }
29            }
30        }
31
32        // Initialize a matrix to hold the stamp count for each cell
33        vector<vector<int>> stampCount(rows + 1, vector<int>(cols + 1));
34        for (int i = 0; i < rows; ++i) {
35            for (int j = 0; j < cols; ++j) {
36                // Calculate the cumulative stamp count for the current cell
37                stampCount[i + 1][j + 1] = stampCount[i + 1][j] + stampCount[i][j + 1] - stampCount[i][j] + diff[i][j];
38                // If the current cell is empty and has no stamps, return false
39                if (grid[i][j] == 0 && stampCount[i + 1][j + 1] == 0) return false;
40            }
41        }
42
43        // If all constraints are satisfied, it's possible to stamp
44        return true;
45    }
46};
47
1// Function to determine if it's possible to stamp the entire grid
2// with a stamp of given height and width.
3/**
4 * @param grid - 2D grid representing the areas to be stamped (0) or not (1)
5 * @param stampHeight - Height of the stamp
6 * @param stampWidth - Width of the stamp
7 * @returns boolean indicating whether it's possible to stamp the whole grid
8 */
9function possibleToStamp(grid: number[][], stampHeight: number, stampWidth: number): boolean {
10    const m: number = grid.length;
11    const n: number = grid[0].length;
12  
13    // Initialize prefixes sums arrays with zeros
14    let prefixSums: number[][] = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0));
15    let diff: number[][] = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0));
16    let count: number[][] = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0));
17  
18    // Compute the prefix sums of the grid
19    for (let i = 0; i < m; ++i) {
20        for (let j = 0; j < n; ++j) {
21            prefixSums[i + 1][j + 1] = prefixSums[i + 1][j] + prefixSums[i][j + 1] - prefixSums[i][j] + grid[i][j];
22        }
23    }
24  
25    // Determine where stamping is possible and mark in the diff array
26    for (let i = 0; i < m; ++i) {
27        for (let j = 0; j < n; ++j) {
28            if (grid[i][j] == 0) {
29                let x: number = i + stampHeight;
30                let y: number = j + stampWidth;
31                if (x <= m && y <= n && prefixSums[x][y] - prefixSums[i][y] - prefixSums[x][j] + prefixSums[i][j] == 0) {
32                    diff[i][j]++;
33                    diff[i][y]--;
34                    diff[x][j]--;
35                    diff[x][y]++;
36                }
37            }
38        }
39    }
40  
41    // Calculate the influence of stamping using prefix sums of the diff array
42    for (let i = 0; i < m; ++i) {
43        for (let j = 0; j < n; ++j) {
44            count[i + 1][j + 1] = count[i + 1][j] + count[i][j + 1] - count[i][j] + diff[i][j];
45            if (grid[i][j] == 0 && count[i + 1][j + 1] == 0) {
46                return false; // Unstamped cell found, stamping not possible
47            }
48        }
49    }
50  
51    return true; // All cells can be stamped
52}
53
54// Example usage
55// let result: boolean = possibleToStamp([[1, 0, 0, 0], [1, 0, 0, 0], [1, 1, 1, 0]], 2, 2);
56// console.log(result); // Should return true or false based on stampability of grid
57
Not Sure What to Study? Take the 2-min Quiz:

The three-steps of Depth First Search are:

  1. Identify states;
  2. Draw the state-space tree;
  3. DFS on the state-space tree.

Time and Space Complexity

Time Complexity

The time complexity of the code is determined by several nested loops that iterate over the entire grid and the use of prefix sums.

  1. The first double loop (calculating s[i + 1][j + 1]) iterates through all m * n cells of the grid once, thus it has a complexity of O(m * n).

  2. The second double loop (calculating d[i][j] and checking conditions) also iterates through all m * n cells of the grid once. Inside this loop, it performs constant-time operations and a check that involves accessing the precomputed prefix sum array s. Again, the complexity is O(m * n).

  3. The last double loop (calculating cnt[i + 1][j + 1] and verifying that there are no zeros uncovered by stamps) iterates through all m * n cells of the grid. Since it only performs constant-time operations, the complexity is O(m * n).

Since these loops are sequential and not nested within each other (other than the initialization loops for prefix sums which also have the same complexity), the overall time complexity is the sum of the individual complexities, which remains O(m * n) because the number of iterations is proportional to the size of the grid.

Space Complexity

The space complexity is determined by additional arrays s, d, and cnt which are used for computing prefix sums and keeping track of the stamps.

  1. The array s has dimensions (m + 1) x (n + 1) which adds up to a space complexity of O((m + 1) * (n + 1)). However, when considering Big O notation, constant factors are dropped, so the complexity is O(m * n).

  2. Similarly, the arrays d and cnt also have dimensions (m + 1) x (n + 1), contributing an additional O(m * n) space complexity each.

The space needed for the input array grid is not considered in the space complexity analysis since it is the input to the function, not additional space allocated by the function itself.

In total, since all three arrays are maintained independently, the overall space complexity is O(3 * m * n), which simplifies to O(m * n) under Big O notation since constant factors are ignored.

Therefore, the final space complexity is O(m * n).

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

Fast Track Your Learning with Our Quick Skills Quiz:

Which data structure is used in a depth first search?


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 👨‍🏫