1314. Matrix Block Sum
Problem Description
Given a matrix mat
with dimensions m x n
and an integer k
, the task is to compute a new matrix answer
of the same dimensions. For each cell answer[i][j]
in the new matrix, we must calculate the sum of all elements within a square block with side length 2k + 1
, centered at mat[i][j]
. The boundaries of the square are constrained by the limits of the array, meaning we cannot go outside the matrix when calculating the sum. The result for answer[i][j]
should only include the sum of valid elements from mat
that lie within the specified range and within the matrix.
Intuition
The brute-force approach to solving this problem would involve iterating over each cell of the matrix, and for each cell, iteratively summing all the elements in the K-radius square. This would require a large number of redundant computations and would not be efficient, particularly for large matrices or values of K.
To arrive at a more efficient solution, we can use dynamic programming, specifically the concept of 2D prefix sums. The idea here is to preprocess the input matrix to generate a prefix sum matrix where each element pre[i][j]
stores the sum of all elements in the rectangle defined by the upper left corner (0,0)
and the lower right corner (i-1, j-1)
of the original matrix. With this prefix sum matrix, we can compute the sum of any submatrix in constant time, using the inclusion-exclusion principle.
The steps to implement this are as follows:
-
Build the prefix sum matrix (
pre
) where the sum up to each point(i, j)
is calculated by adding the current element to the sum of the elements above and to the left, subtracting the sum of the overlapping corner (already counted twice). -
Define a helper function
get(i, j)
to safely retrieve the value ofpre[i][j]
, handling boundary cases to stay within the matrix dimensions. -
Loop through each cell
(i, j)
in the original matrix, and for each cell, calculate the sum of the K-radius square around it using the prefix sum matrix and the helper function. This involves adding and subtracting values from the four corners of the block surrounding(i, j)
, constrained byk
.
The result is an efficient algorithm that computes the desired answer
matrix with much fewer operations compared to the brute-force method, thus making it scalable to larger matrices and values of k
. The overall time complexity is O(m * n)
for the sum matrix construction plus O(m * n)
for calculating the block sums, and the space complexity is O(m * n)
for storing the prefix sums.
Learn more about Prefix Sum patterns.
Solution Approach
The solution to the problem uses dynamic programming with a 2D prefix sum matrix to optimize the process of computing the sums of submatrices. Let's break down the steps taken in the provided solution code:
-
Initial Prefix Sum Matrix Generation: A 2D prefix sum matrix
pre
is generated wherepre[i][j]
will contain the sum of all elements from the top-left corner(0, 0)
to the position(i-1, j-1)
in the original matrixmat
. This matrixpre
is of size(m + 1) x (n + 1)
to handle the prefix sum calculation conveniently, even for the cells on the topmost row and leftmost column.The prefix sum is calculated as follows:
pre[i][j] = pre[i - 1][j] + pre[i][j - 1] - pre[i - 1][j - 1] + mat[i - 1][j - 1]
This equation accounts for the current element and the sums above and to the left, removing the overlapped corner that was added twice.
-
Safe Prefix Sum Retrieval Function (
get
): Theget
function is defined to safely retrieve values from thepre
matrix, taking care of the boundaries, and ensuring that indices outside the matrix bounds map to the nearest valid index. This function ensures that when we perform the sum calculation, we do not go outside the matrix.def get(i, j): i = max(min(m, i), 0) j = max(min(n, j), 0) return pre[i][j]
-
Computing the Block Sums: We then iterate over each cell
(i, j)
in the matrixmat
. The sum for eachanswer[i][j]
is computed using theget
function to obtain values from the prefix sum matrix corresponding to the corners of the K-radius block:ans[i][j] = ( get(i + k + 1, j + k + 1) - get(i + k + 1, j - k) - get(i - k, j + k + 1) + get(i - k, j - k) )
This step applies the inclusion-exclusion principle to subtract the sums outside the K-radius block by considering the bottom right corner
(i + k + 1, j + k + 1)
, the top right(i + k + 1, j - k)
, top left(i - k, j - k)
, and bottom left corners(i - k, j + k + 1)
.
The matrixBlockSum
function ultimately returns the computed ans
matrix, which represents the sum of elements within k
distance from each element in the original mat
.
The code structure provided makes use of a 2D prefix sum matrix and carefully handles edge cases with the get
function, ensuring the solution is clean, efficient, and robust for the given problem.
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 use a simple 3x3 matrix as an example to illustrate how the solution approach works, with k=1
:
Original matrix mat
:
| 1 | 2 | 3 | | 4 | 5 | 6 | | 7 | 8 | 9 |
-
Initial Prefix Sum Matrix Generation: We first build an enlarged prefix sum matrix
pre
of size 4x4 to include a buffer for handling the edges. The construction would look like this after using the described formula:Initial
pre
matrix filled with zeros (for illustration):| 0 | 0 | 0 | 0 | | 0 | | | | | 0 | | | | | 0 | | | |
After calculating the prefix sums:
| 0 | 0 | 0 | 0 | | 0 | 1 | 3 | 6 | | 0 | 5 |12 |21 | | 0 |12 |27 |45 |
You can see that
pre[3][3]
has a value of45
, which is the sum of all elements ofmat
. -
Safe Prefix Sum Retrieval Function (
get
): Theget
function is used to safely access elements in thepre
matrix, ensuring that we do not access indices outside the bounds of the matrix. -
Computing the Block Sums: We compute the sums for each cell in
ans
consideringk=1
. For the central elementans[1][1]
corresponding tomat[1][1]
which is5
, the calculation using the corners would be:ans[1][1] = ( get(1 + 1 + 1, 1 + 1 + 1) - get(1 + 1 + 1, 1 - 1) - get(1 - 1, 1 + 1 + 1) + get(1 - 1, 1 - 1) ) ans[1][1] = pre[3][3] - pre[3][1] - pre[1][3] + pre[1][1] ans[1][1] = 45 - 12 - 6 + 1 ans[1][1] = 28
The
get
function ensures we don't index intopre
at negative indices or beyond its size by clamping the values appropriately.The complete
ans
matrix would be computed similarly, for example, the top-left cornerans[0][0]
would be computed as:ans[0][0] = ( get(0 + 1 + 1, 0 + 1 + 1) - get(0 + 1 + 1, 0 - 1) - get(0 - 1, 0 + 1 + 1) + get(0 - 1, 0 - 1) ) ans[0][0] = pre[2][2] - pre[2][0] - pre[0][2] + pre[0][0] ans[0][0] = 12 - 0 - 0 + 0 ans[0][0] = 12
Which is the sum of the 2x2 block that is within
k
distance from the top-left element1
.
Final matrix ans
after computing sums for all other cells:
| 12 | 21 | 16 | | 27 | 45 | 33 | | 24 | 39 | 28 |
This walk-through demonstrates the computational steps of the algorithm using the example matrix. Notice how the sums reflect the areas around each cell, constrained by k
and the bounds of the matrix.
Solution Implementation
1from typing import List
2
3class Solution:
4 def matrixBlockSum(self, mat: List[List[int]], k: int) -> List[List[int]]:
5 # Get the dimensions of the matrix
6 num_rows, num_cols = len(mat), len(mat[0])
7
8 # Initialize a prefix sum matrix with one extra row and column
9 prefix_sum = [[0] * (num_cols + 1) for _ in range(num_rows + 1)]
10
11 # Populate the prefix sum matrix
12 for row in range(1, num_rows + 1):
13 for col in range(1, num_cols + 1):
14 prefix_sum[row][col] = (
15 prefix_sum[row - 1][col] +
16 prefix_sum[row][col - 1] -
17 prefix_sum[row - 1][col - 1] +
18 mat[row - 1][col - 1]
19 )
20
21 # Helper function to get the cumulative sum up to (i, j) in the prefix sum matrix
22 def get_cumulative_sum(i, j):
23 # Boundary check to ensure indices fall within the valid range
24 i = max(min(num_rows, i), 0)
25 j = max(min(num_cols, j), 0)
26 return prefix_sum[i][j]
27
28 # Initialize the answer matrix with zeros
29 answer = [[0] * num_cols for _ in range(num_rows)]
30
31 # Compute the block sum for each element in the matrix
32 for row in range(num_rows):
33 for col in range(num_cols):
34 answer[row][col] = (
35 get_cumulative_sum(row + k + 1, col + k + 1) -
36 get_cumulative_sum(row + k + 1, col - k) -
37 get_cumulative_sum(row - k, col + k + 1) +
38 get_cumulative_sum(row - k, col - k)
39 )
40
41 # Return the answer matrix
42 return answer
43
1class Solution {
2 private int[][] prefixSum; // 2D array to hold the prefix sum of matrix
3 private int numRows;
4 private int numCols;
5
6 /**
7 * Calculates the block sum of each element in the matrix surrounded by 'k' distance.
8 * @param mat The original matrix of integers
9 * @param k The given distance around the element to calculate sum
10 * @return The matrix representing the block sum of each element
11 */
12 public int[][] matrixBlockSum(int[][] mat, int k) {
13 numRows = mat.length; // number of rows in the input matrix
14 numCols = mat[0].length; // number of columns in the input matrix
15
16 // Define a new matrix with additional row and column to easily compute prefix sum
17 prefixSum = new int[numRows + 1][numCols + 1];
18
19 // Populating the prefix sum matrix
20 for (int i = 1; i <= numRows; ++i) {
21 for (int j = 1; j <= numCols; ++j) {
22 // Each cell is the sum of values above it, to its left,
23 // minus the cell diagonally up and left to remove the double count, plus the current cell
24 prefixSum[i][j] = prefixSum[i - 1][j] + prefixSum[i][j - 1] - prefixSum[i - 1][j - 1] + mat[i - 1][j - 1];
25 }
26 }
27
28 // Create a matrix to hold answers
29 int[][] answer = new int[numRows][numCols];
30
31 // Calculating the block sum for each cell
32 for (int i = 0; i < numRows; ++i) {
33 for (int j = 0; j < numCols; ++j) {
34 // Calculate the sum of the block using prefix sum
35 answer[i][j] = getSum(i + k + 1, j + k + 1) - getSum(i + k + 1, j - k) - getSum(i - k, j + k + 1) + getSum(i - k, j - k);
36 }
37 }
38 return answer;
39 }
40
41 /**
42 * Get the prefix sum of the cell (i, j) ensuring the indices are within bounds.
43 * @param i The row index in the prefix sum matrix
44 * @param j The column index in the prefix sum matrix
45 * @return The prefix sum value of the cell (i, j) bounded properly
46 */
47 private int getSum(int i, int j) {
48 // Bound the indices within the limits of the prefixSum matrix
49 i = Math.max(Math.min(numRows, i), 0);
50 j = Math.max(Math.min(numCols, j), 0);
51 return prefixSum[i][j];
52 }
53}
54
1class Solution {
2public:
3 vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) {
4 int rows = mat.size(), cols = mat[0].size();
5 vector<vector<int>> prefixSum(rows + 1, vector<int>(cols + 1));
6
7 // Compute 2D prefix sum
8 for (int i = 1; i <= rows; ++i) {
9 for (int j = 1; j <= cols; ++j) {
10 prefixSum[i][j] = prefixSum[i - 1][j]
11 + prefixSum[i][j - 1]
12 - prefixSum[i - 1][j - 1]
13 + mat[i - 1][j - 1];
14 }
15 }
16
17 vector<vector<int>> answer(rows, vector<int>(cols));
18
19 // Iterate through all the cells of the matrix
20 for (int i = 0; i < rows; ++i) {
21 for (int j = 0; j < cols; ++j) {
22 // Calculate block sum using prefixSum
23 answer[i][j] = getBlockSum(i + k + 1, j + k + 1, rows, cols, prefixSum)
24 - getBlockSum(i + k + 1, j - k, rows, cols, prefixSum)
25 - getBlockSum(i - k, j + k + 1, rows, cols, prefixSum)
26 + getBlockSum(i - k, j - k, rows, cols, prefixSum);
27 }
28 }
29
30 return answer;
31 }
32
33 int getBlockSum(int row, int col, int maxRow, int maxCol, vector<vector<int>>& prefixSum) {
34 // Clamp row and column indices to the valid range
35 row = max(min(maxRow, row), 0);
36 col = max(min(maxCol, col), 0);
37
38 // Return the value at the bounded prefix sum location
39 return prefixSum[row][col];
40 }
41};
42
1// Define the matrix type as an array of arrays of numbers
2type Matrix = number[][];
3
4// Calculate the 2D prefix sum matrix
5function buildPrefixSumMatrix(mat: Matrix): Matrix {
6 const rows = mat.length, cols = mat[0].length;
7 const prefixSum: Matrix = Array.from({ length: rows + 1 }, () => Array(cols + 1).fill(0));
8
9 // Compute the 2D prefix sum
10 for (let i = 1; i <= rows; ++i) {
11 for (let j = 1; j <= cols; ++j) {
12 prefixSum[i][j] = prefixSum[i - 1][j] + prefixSum[i][j - 1]
13 - prefixSum[i - 1][j - 1] + mat[i - 1][j - 1];
14 }
15 }
16
17 return prefixSum;
18}
19
20// Given the row and column indices, return the prefix sum within the bounded indices
21function getBlockSum(row: number, col: number, maxRow: number, maxCol: number, prefixSum: Matrix): number {
22 // Clamp row and column indices to the valid range
23 row = Math.max(Math.min(maxRow, row), 0);
24 col = Math.max(Math.min(maxCol, col), 0);
25
26 // Return the value at the bounded prefix sum location
27 return prefixSum[row][col];
28}
29
30// Computes the matrix block sum given the original matrix and the value k
31function matrixBlockSum(mat: Matrix, k: number): Matrix {
32 const rows = mat.length, cols = mat[0].length;
33 const prefixSum = buildPrefixSumMatrix(mat);
34 const answer: Matrix = Array.from({ length: rows }, () => Array(cols).fill(0));
35
36 // Iterate through all the cells of the matrix
37 for (let i = 0; i < rows; ++i) {
38 for (let j = 0; j < cols; ++j) {
39 // Calculate block sum using prefixSum
40 answer[i][j] = getBlockSum(i + k + 1, j + k + 1, rows, cols, prefixSum)
41 - getBlockSum(i + k + 1, j - k, rows, cols, prefixSum)
42 - getBlockSum(i - k, j + k + 1, rows, cols, prefixSum)
43 + getBlockSum(i - k, j - k, rows, cols, prefixSum);
44 }
45 }
46
47 return answer;
48}
49
Time and Space Complexity
Time Complexity:
The time complexity of the given code can be analyzed in two major parts: the construction of the prefix sum matrix pre
and the computation of the answer matrix ans
.
-
Prefix Sum Matrix Computation: Constructing the prefix sum matrix requires iterating through each element of the original
m x n
matrixmat
, and for each element, a constant amount of work is performed (adding and subtracting values). Therefore, the time complexity for this part isO(m * n)
. -
Answer Matrix Computation: The computation of the answer matrix also involves iterating through all elements in the
m x n
matrix and performing a constant amount of work for each to calculate the sum of the block using the prefix sum matrix. Hence, the time complexity for this part is alsoO(m * n)
.
Combining both parts, the overall time complexity remains O(m * n)
because both parts are sequentially executed and have the same order of complexity.
Space Complexity:
The space complexity can be analyzed as follows:
-
Prefix Sum Matrix: An additional
m+1 x n+1
matrixpre
is used for the prefix sums, which gives us a space complexity ofO((m + 1) * (n + 1))
. -
Answer Matrix: The answer matrix
ans
is of the same size as the input matrixmat
, i.e.,m x n
. Since it is used to store the final output, it might not be counted towards additional space in some analysis contexts. But for a comprehensive analysis, we consider it and, therefore,O(m * n)
.
When combined, the term O((m + 1) * (n + 1))
is dominant. However, since the +1
is a constant and does not change the order of growth, the overall space complexity simplifies to O(m * n)
.
Learn more about how to find time and space complexity quickly using problem constraints.
How does merge sort divide the problem into subproblems?
Recommended Readings
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
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
Want a Structured Path to Master System Design Too? Don’t Miss This!