Today's task is to obtain all of the possible squares in the provided matrix. Before we get started, the question we'll be looking up today is similar to the one we looked up previously, Maximal Square.
Since today’s problem is based on dynamic programming, it is recommended to have basic knowledge of it for better understanding.
Now, let's go into the specifics of the problem:
Problem Statement
Given an m * n matrix of ones and zeros, return how many square submatrices have all ones.
Example 1
Input
matrix =
Output
10
Explanation
There are 8 squares of side 1.
There are four squares on side 2.
There is 0 square on side 3.
Total number of squares = 8 + 2 + 0 = 10.
Example 2
Input
matrix =
Output
21
Explanation
There are 13 squares of side 1.
There is 6 square of side 2.
There is 2 square of side 3.
Total number of squares = 13 + 6 + 2 = 21.
Approach
The idea is to simply traverse the matrix from the last element to the first element, treating each traversed element as a top-left square element and counting how many squares can be made using each element.
Step 1:- Make a function countSquares( matrix[][] ), which accepts a matrix and returns the final count of the squares.
Step 1.1:- Inside the function, initialize the count as 0.
Step 1.2:- Declare the auxiliary matrix with the same size as the passed matrix. Suppose the user provides the below matrix:-
Thus, the auxiliary matrix will look like the following:-
Step 1.3:- Traverse through the passed matrix from the last index until the pointer reaches the first index.
Step 1.3.1:- Add each traversed element to the auxiliary matrix( dp[][] ).
Step 1.3.2:- Increment the count each time when the traversed element is 1.
Step 1.3.3:- For each traversed element check whether it lies outside the last row or last column,
If yes, find the minimum element from the current element’s next, bottom, and diagonal element of the dp[ ] [ ] matrix.
Add 1 to the minimum element and overwrite the prewritten value in dp[][] matrix.
Step 1.3.4:- Check if the overwritten value is greater than 1 or not. If yes, then update the count. count -> count + (dp[i][j]-1)
Step 1.4:- Return the count to the respected function call.
Dry Run
arr = {{1,1,1,1}, {1,1,0,1}, {0,1,0,1}}:
Define count to be 0.
Get the number of rows and columns in the input matrix arr.
rows = 3 (number of rows in arr)
cols = 4 (number of columns in arr)
Define a new auxiliary matrix dp with the same number of rows and columns as the input matrix arr.
dp = {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}
Traverse the input matrix arr from the last element to the first element.
And count increase one more for 2x2 matrix.
Total count is 10
Implementation in Java
import java.io.*;
import java.util.*;
public class CountSquaresSubmatrices {
public static int countSquares(int[][] matrix) {
// there can be a case where the matrix has no elements
int count = 0;
// take how many numbers of rows are there
int rows = matrix.length;
// take how many numbers of columns are there
int cols = matrix[0].length;
// define an auxiliary matrix to keep track of the formation of squares
int[][] dp = new int[rows][cols];
// traverse the passed matrix from the last element until it reaches the first element
for(int i = rows-1; i>=0; i--){
for(int j = cols-1; j>=0; j--){
// store each element being traversed into the auxiliary matrix
dp[i][j] = matrix[i][j];
// If the current index of auxiliary matrix holding the 1's, increment the count by 1
if(dp[i][j] == 1){
count++;
}
// Overwrite those values which are not in the last row or last columns
if(i<rows-1 && j<cols-1 && matrix[i][j]!=0){
/*
find the minimum among the current index's next element,
bottom element, and diagonal element of dp[][]
and update dp[][] matrix with the minimum value + 1.
*/
dp[i][j] = 1 + Math.min(Math.min(dp[i+1][j] , dp[i][j+1]), dp[i+1][j+1] );
/*
there can be a case of formation of the large
square, i.e., more than 1 side,
if that is so, update the count
for example:- if the dp[i][j] contains "2"
it means [ 1 , 1 ],[ 1 , 1 ], they all are forming 1 side of
square individually, and together they are forming a square of side 2
hence, count is updated by (dp[i][j]-1) or (2-1)= 1 in this example.
*/
if(dp[i][j] > 1){
count = count + (dp[i][j] - 1);
}
}
}
}
// return the count to the function call
return count;
}
public static void main(String[] args) {
// Hardcoded input:
int[][] arr = {{1,1,1,1}, {1,1,0,1}, {0,1,0,1}};
// function call
System.out.println("Count Squares Sub matrices with all ones :- " + countSquares(arr));
}
}
You can also try this code with Online Java Compiler
// c++ program to count square submatrices with all ones
#include <bits/stdc++.h>
using namespace std;
int countSquares(vector<vector<int>>& matrix) {
// there can be a case where the matrix has no elements
int count = 0;
// take how many numbers of rows are there
int rows = matrix.size();
// take how many numbers of columns are there
int cols = matrix[0].size();
// define an auxiliary matrix to keep track of the formation of squares
int dp[rows][cols];
// traverse the passed matrix from the last element until it reaches the first element
for(int i=rows-1 ; i>=0 ;i--){
for(int j=cols-1 ; j>=0; j--){
// store each element being traversed into the auxiliary matrix
dp[i][j] = matrix[i][j];
// If the current index of auxiliary matrix holding the 1's, increment the count by 1
if(dp[i][j] == 1){
count++;
}
// Overwrite those values which are not in the last row or last columns
if(i < rows-1 && j < cols-1 && matrix[i][j]!=0){
/*
find the minimum among the current index's next element,
bottom element, and diagonal element of dp[][]
and update dp[][] matrix with the minimum value + 1.
*/
dp[i][j] = 1 + min(min(dp[i][j+1] , dp[i+1][j]), dp[i+1][j+1]);
/*
there can be a case of formation of the large
square, i.e., more than 1 side,
if that is so, update the count
for example:- if the dp[i][j] contains "2"
it means [ 1 , 1 ],[ 1 , 1 ], they all are forming 1 side of
square individually, and together they are forming a square of side 2
hence, count is updated by (dp[i][j]-1) or (2-1)= 1 in this example.
*/
if(dp[i][j] > 1){
count = count + (dp[i][j]-1);
}
}
}
}
// return the count to the function call
return count;
}
int main() {
// declare the multi dimensional vector
vector<vector<int>> v { {1, 1, 1, 1}, {1, 1, 0, 1}, {0, 1, 0, 1} };
// functional call
cout<<"Count Square Submatrices with all ones : "<<countSquares(v);
return 0;
}
Output
Time Complexity: O(rows*cols), where rows and cols are the total number of rows and the columns in a matrix.
Space Complexity: O(rows*cols), as we initialised the additional matrix with the same size of the given matrix.
Frequently Asked Questions
What is memoisation in dynamic programming?
Memoisation is a standard methodology for dynamic programming problems. The answer is solutions to the same problem with smaller inputs (like in the Fibonacci problem).
What are the applications of dynamic programming?
Here are some problems: 0/1 knapsack problem, Mathematical optimisation problem, All pair shortest path problem, Time-sharing: It schedules the job to maximise CPU usage, Reliability design problem, Longest common subsequence (LCS), Flight control and robotics control.
What is the difference between the feasible solution and the optimal solution?
A feasible solution meets all of the constraints of the problem. When maximising, an optimal solution is a feasible solution that yields the highest attainable objective function value (or smallest when minimising).
What does the optimality principle imply in dynamic programming?
Dynamic programming uses the optimality principle to solve complex problems by breaking them into smaller subproblems, solving each subproblem optimally, and combining their solutions.
What makes dynamic programming better than others?
Dynamic programming reduces repetition and increases runtime efficiency by trading space for time. Solution storing takes up space not needed in recursive solutions.
Conclusion
To wrap up the session, we’ve looked upon one more question often asked in interviews, i.e., Count Submatrices with All Ones. We’ve also analyzed the complexity of the approach. Continue to try such questions to have a better knowledge of Dynamic programming. Other approaches may have been used to tackle the situation, but they resulted in much more complexities. Understanding Dynamic programming principles is critical since it is the most frequently asked question during interviews.
Nonetheless, if you continue to attempt such questions and stick to the guided path, you can easily ace your interview.
There are several resources available on the market, but a well-structured strategy is all that is required. Right?
As a result, check out our top-tier courses taught by the greatest teachers and have fun coding.