Sum of sub-matrices using dynamic programming
By: Syphax Ait Oubelli
Tags: Time Complexity, Algorithms, Dynamic Programming, Arrays, Data Structures

Given a non-empty matrix (2D array) like the one below, we are asked to return a new matrix (that we can name sumMat for example) where each element sumMat[i][j] represents the sum of elements of the sub-matrix that goes from mat[0][0] to mat[i][j]

mat

For example, sumMat[2][1] would be equal to the sum of elements of this sub-matrix:

In other words, sumMat[2][1] = 3 + 2 + 4 + (-5) + 7 + 1 = 12

Solution 1: Brute Force

Using brute force method to solve this problem consists of always calculating sumMat[i][j] from the beginning. By doing that, we will end up with four nested loops (two first ones to iterate over elements of sumMat and two last ones to count sumMat[i][j]), so the time complexity would be O((n*m)²), where n represents the number of rows and m the number of columns.

First method implementation in JavaScript

But you can notice that we went through each element of the original matrix many times, thing that we can avoid by using the second solution.

Solution 2: Dynamic programming

In this method, each element of sumMat is calculated based on values of elements we calculated before it.

Explanation:

To better visualize the solution, we gonna decompose our result matrix into four parts like this:

sumMat

Part 1:

The first part contains only the first element (mat[0][0]), so there is no calculations to do because sumMat[0][0] = mat[0][0], this one is like a base case. So sumMat now looks like this:

sumMat after filling the first part

Part 2:

The second part contains the remaining elements of the first row.

Let’s take for example sumMat[0][1], sumMat[0][1] is equal to mat[0][1] + mat[0][0], we can replace mat[0][0] by sumMat[0][0] because they have the same value.

So sumMat[0][1] = mat[0][1] + sumMat[0][0].

Let’s move to summat[0][2], summat[0][2] is equal to mat[0][2] + (mat[0][1] + mat[0][0]), but you can notice that we calculated (mat[0][1] + mat[0][0]) just before, it’s sumMat[0][1].

So sumMat[0][2] = mat[0][2] + sumMat[0][1].

And we can use this technique over and over to fill all elements of part 2, because each element will give us the possibility to calculate the next one, because in a general way: sumMat[0][i] = mat[0][i] + sumMat[0][i-1].

How is part 2 filled

Part 3:

The third part contains the remaining elements of the first column.

We will apply the same technique we used for the part 2, so sumMat[i][0] = mat[i][0] + sumMat[i-1][0].

How is part 3 filled

After filling part 1, 2, and 3, sumMat now looks like this:

sumMat after filling part 1, 2, and 3

Part 4:

This part contains the remaining elements.

Let’s take for example sumMat[1][1]:

We can see that sumMat[1][1] is equal to mat[1][1] + mat[0][1] + mat[1][0] + mat[0][0]. But our goal is to use values we already calculated to avoid always starting from the beginning. So we can write sumMat[1][1] as mat[1][1] + sumMat[0][1] + sumMat[1][0] — sumMat[0][0]. Don’t worry, I’m gonna show you the reason in two ways:

Mathematically:

Introducing an element to an equation then subtracting it later doesn’t change the value (e.g.: x is equal to x +1–1), so:

Mathematical demonstration

Graphically:

You can see in the picture below that sumMat[0][1] and sumMat[1][0] are overlapping, so if we add sumMat[0][1] and sumMat[1][0] together, the intersection will be counted twice, and we can avoid this problem by subtracting the intersection (which is sumMat[0][0]) once, and we get that sumMat[1][1] = mat[1][1] + sumMat[0][1] + sumMat[1][0] — sumMat[0][0]

And we can apply the same technique for the remaining elements in part 4 because in a general way: sumMat[i][j] = mat[i][j] + sumMat[i-1][j] + sumMat[i][j-1] — sumMat[i-1][j-1].

This solution’s time complexity is O(n*m), which is way faster than O((n*m)²).

Second method implementation in JavaScript

Final result:

sumMat fully filled

Now you would think: “why is the second method better when both methods give the same result?”.

In fact, both methods return the right answer, but as mentioned earlier, the second method is way faster than the first one, and we will prove it by comparing time needed to return the result of a 100*100 matrix.

Step 1:

Let’s generate a 100*100 matrix of random numbers between 0 and 9:

Step 2:

Let’s set a timer on both methods:

Step 3:

Let’s call both methods with the 100*100 matrix:

Method 1:

Method 2:

You can see that the first method took around 74ms while the second one took less than 4ms, the difference is huge, so imagine if n and m were way bigger than 100! This is why time complexity is important, and it’s better to avoid brute force method, especially when dealing with large quantities of data.

P.S: This is my first Medium article, so knowing your review about it and what to improve would really help me!