1605. Find Valid Matrix Given Row and Column Sums
Problem Description
You need to reconstruct a 2D matrix given only the sum of each row and the sum of each column.
You are given:
rowSum
: an array whererowSum[i]
represents the sum of all elements in thei
-th row of the matrixcolSum
: an array wherecolSum[j]
represents the sum of all elements in thej
-th column of the matrix
Your task is to find any valid matrix of non-negative integers with dimensions rowSum.length Γ colSum.length
that satisfies both constraints:
- The sum of elements in each row
i
equalsrowSum[i]
- The sum of elements in each column
j
equalscolSum[j]
The problem guarantees that at least one valid solution exists. You can return any matrix that meets these requirements.
For example, if rowSum = [3, 8]
and colSum = [4, 7]
, one possible valid matrix would be:
[[3, 0], [1, 7]]
where row 0 sums to 3, row 1 sums to 8, column 0 sums to 4, and column 1 sums to 7.
Intuition
The key insight is that we can build the matrix greedily by filling each cell with the maximum possible value without violating the constraints.
Think of it this way: at each position (i, j)
, we need to place a value that contributes to both rowSum[i]
and colSum[j]
. The maximum value we can place is min(rowSum[i], colSum[j])
- we can't exceed what's left in either the row sum or column sum.
Why does this greedy approach work? Consider what happens when we place x = min(rowSum[i], colSum[j])
at position (i, j)
:
- If
rowSum[i] β€ colSum[j]
, we completely satisfy the remaining requirement for rowi
and partially fulfill columnj
- If
colSum[j] < rowSum[i]
, we completely satisfy the remaining requirement for columnj
and partially fulfill rowi
After placing this value, we subtract it from both rowSum[i]
and colSum[j]
to track the remaining sums needed.
The beauty of this approach is that it guarantees progress - at each step, we're either completing a row's requirement or a column's requirement (or both). Since the total sum of all row sums equals the total sum of all column sums (which the problem guarantees by stating a solution exists), we'll eventually reduce all sums to zero.
By traversing the matrix row by row and applying this greedy strategy at each cell, we systematically construct a valid matrix. The algorithm naturally handles the distribution of values across the matrix without needing complex backtracking or optimization.
Learn more about Greedy patterns.
Solution Approach
The implementation follows a straightforward greedy construction approach:
-
Initialize the answer matrix: Create an
m Γ n
matrix filled with zeros, wherem = len(rowSum)
andn = len(colSum)
. -
Iterate through each position: Use nested loops to traverse each cell
(i, j)
in the matrix from top-left to bottom-right. -
Apply the greedy strategy: At each position
(i, j)
:- Calculate
x = min(rowSum[i], colSum[j])
- this is the maximum value we can place without violating constraints - Set
ans[i][j] = x
- Update the remaining sums:
rowSum[i] -= x
andcolSum[j] -= x
- Calculate
The algorithm works row by row, filling each position optimally. Let's trace through why this works:
- When we process position
(i, j)
,rowSum[i]
represents how much sum is still needed for rowi
, andcolSum[j]
represents how much sum is still needed for columnj
. - By taking the minimum, we ensure we don't exceed either constraint.
- After processing all
n
columns in rowi
, we're guaranteed thatrowSum[i] = 0
because the sum of allcolSum
values initially equals the sum of allrowSum
values. - Similarly, after processing all
m
rows, eachcolSum[j]
will also reach0
.
The time complexity is O(m Γ n)
since we visit each cell exactly once. The space complexity is O(m Γ n)
for storing the answer matrix.
This greedy approach eliminates the need for backtracking or complex optimization because each local decision (placing the minimum value) leads to a globally valid solution. The algorithm essentially distributes the required sums across the matrix in a way that satisfies both row and column constraints simultaneously.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the algorithm with rowSum = [5, 7]
and colSum = [4, 2, 6]
.
We need to create a 2Γ3 matrix where:
- Row 0 sums to 5, Row 1 sums to 7
- Column 0 sums to 4, Column 1 sums to 2, Column 2 sums to 6
Initial Setup:
- Create a 2Γ3 matrix filled with zeros
- Working arrays:
rowSum = [5, 7]
,colSum = [4, 2, 6]
Step-by-step Construction:
Position (0,0):
min(rowSum[0], colSum[0]) = min(5, 4) = 4
- Place 4 at position (0,0)
- Update:
rowSum[0] = 5-4 = 1
,colSum[0] = 4-4 = 0
- Matrix:
[[4, _, _], [_, _, _]]
Position (0,1):
min(rowSum[0], colSum[1]) = min(1, 2) = 1
- Place 1 at position (0,1)
- Update:
rowSum[0] = 1-1 = 0
,colSum[1] = 2-1 = 1
- Matrix:
[[4, 1, _], [_, _, _]]
Position (0,2):
min(rowSum[0], colSum[2]) = min(0, 6) = 0
- Place 0 at position (0,2)
- Update:
rowSum[0] = 0
,colSum[2] = 6
- Matrix:
[[4, 1, 0], [_, _, _]]
Note: Row 0 is now complete with sum = 4+1+0 = 5 β
Position (1,0):
min(rowSum[1], colSum[0]) = min(7, 0) = 0
- Place 0 at position (1,0)
- Update:
rowSum[1] = 7
,colSum[0] = 0
- Matrix:
[[4, 1, 0], [0, _, _]]
Position (1,1):
min(rowSum[1], colSum[1]) = min(7, 1) = 1
- Place 1 at position (1,1)
- Update:
rowSum[1] = 7-1 = 6
,colSum[1] = 1-1 = 0
- Matrix:
[[4, 1, 0], [0, 1, _]]
Position (1,2):
min(rowSum[1], colSum[2]) = min(6, 6) = 6
- Place 6 at position (1,2)
- Update:
rowSum[1] = 6-6 = 0
,colSum[2] = 6-6 = 0
- Matrix:
[[4, 1, 0], [0, 1, 6]]
Final Result:
[[4, 1, 0], [0, 1, 6]]
Verification:
- Row sums: 4+1+0 = 5 β, 0+1+6 = 7 β
- Column sums: 4+0 = 4 β, 1+1 = 2 β, 0+6 = 6 β
The greedy approach successfully constructs a valid matrix by always placing the maximum allowable value at each position, ensuring we never violate the row or column sum constraints.
Solution Implementation
1class Solution:
2 def restoreMatrix(self, rowSum: List[int], colSum: List[int]) -> List[List[int]]:
3 """
4 Restore a matrix where each row sum and column sum matches the given constraints.
5 Uses a greedy approach by filling each cell with the minimum of remaining row and column sums.
6
7 Args:
8 rowSum: List containing the required sum for each row
9 colSum: List containing the required sum for each column
10
11 Returns:
12 A 2D matrix satisfying the row and column sum constraints
13 """
14 # Get dimensions of the target matrix
15 num_rows, num_cols = len(rowSum), len(colSum)
16
17 # Initialize result matrix with zeros
18 result_matrix = [[0] * num_cols for _ in range(num_rows)]
19
20 # Fill the matrix using greedy approach
21 for row_idx in range(num_rows):
22 for col_idx in range(num_cols):
23 # Place the minimum of remaining row sum and column sum at current position
24 # This ensures we don't exceed either constraint
25 value_to_place = min(rowSum[row_idx], colSum[col_idx])
26 result_matrix[row_idx][col_idx] = value_to_place
27
28 # Update remaining sums after placing the value
29 rowSum[row_idx] -= value_to_place
30 colSum[col_idx] -= value_to_place
31
32 return result_matrix
33
1class Solution {
2 /**
3 * Restores a matrix from given row and column sums.
4 * Uses a greedy approach: for each cell, assign the minimum of remaining row sum and column sum.
5 *
6 * @param rowSum Array containing the sum of each row in the target matrix
7 * @param colSum Array containing the sum of each column in the target matrix
8 * @return A valid matrix where each row sums to rowSum[i] and each column sums to colSum[j]
9 */
10 public int[][] restoreMatrix(int[] rowSum, int[] colSum) {
11 // Get dimensions of the matrix
12 int numberOfRows = rowSum.length;
13 int numberOfColumns = colSum.length;
14
15 // Initialize result matrix with zeros
16 int[][] resultMatrix = new int[numberOfRows][numberOfColumns];
17
18 // Iterate through each cell in the matrix
19 for (int row = 0; row < numberOfRows; row++) {
20 for (int col = 0; col < numberOfColumns; col++) {
21 // Assign the minimum of remaining row sum and column sum to current cell
22 // This ensures we don't exceed either constraint
23 int cellValue = Math.min(rowSum[row], colSum[col]);
24 resultMatrix[row][col] = cellValue;
25
26 // Update remaining sums after assignment
27 rowSum[row] -= cellValue;
28 colSum[col] -= cellValue;
29 }
30 }
31
32 return resultMatrix;
33 }
34}
35
1class Solution {
2public:
3 vector<vector<int>> restoreMatrix(vector<int>& rowSum, vector<int>& colSum) {
4 // Get matrix dimensions
5 int numRows = rowSum.size();
6 int numCols = colSum.size();
7
8 // Initialize result matrix with zeros
9 vector<vector<int>> resultMatrix(numRows, vector<int>(numCols));
10
11 // Greedy approach: fill each cell with the minimum of remaining row and column sums
12 for (int row = 0; row < numRows; ++row) {
13 for (int col = 0; col < numCols; ++col) {
14 // Take the minimum value between current row sum and column sum
15 // This ensures we don't exceed either constraint
16 int cellValue = min(rowSum[row], colSum[col]);
17
18 // Assign the value to the current cell
19 resultMatrix[row][col] = cellValue;
20
21 // Update remaining sums by subtracting the assigned value
22 rowSum[row] -= cellValue;
23 colSum[col] -= cellValue;
24 }
25 }
26
27 return resultMatrix;
28 }
29};
30
1/**
2 * Restores a matrix from given row and column sums using a greedy approach.
3 * The algorithm fills each cell with the minimum of the remaining row sum and column sum.
4 *
5 * @param rowSum - Array containing the sum of each row in the target matrix
6 * @param colSum - Array containing the sum of each column in the target matrix
7 * @returns A valid matrix where each row sums to rowSum[i] and each column sums to colSum[j]
8 */
9function restoreMatrix(rowSum: number[], colSum: number[]): number[][] {
10 // Get dimensions of the matrix
11 const rowCount: number = rowSum.length;
12 const columnCount: number = colSum.length;
13
14 // Initialize result matrix with zeros
15 const resultMatrix: number[][] = Array.from(
16 { length: rowCount },
17 () => new Array<number>(columnCount).fill(0)
18 );
19
20 // Fill the matrix using greedy approach
21 for (let row = 0; row < rowCount; row++) {
22 for (let column = 0; column < columnCount; column++) {
23 // Place the minimum of remaining row sum and column sum
24 const valueToPlace: number = Math.min(rowSum[row], colSum[column]);
25 resultMatrix[row][column] = valueToPlace;
26
27 // Update remaining sums
28 rowSum[row] -= valueToPlace;
29 colSum[column] -= valueToPlace;
30 }
31 }
32
33 return resultMatrix;
34}
35
Time and Space Complexity
Time Complexity: O(m Γ n)
The algorithm uses two nested loops. The outer loop iterates m
times (number of rows), and the inner loop iterates n
times (number of columns). Inside the nested loops, all operations (finding minimum, assignment, and subtraction) are performed in constant time O(1)
. Therefore, the overall time complexity is O(m Γ n)
.
Space Complexity: O(m Γ n)
The algorithm creates a 2D matrix ans
with dimensions m Γ n
to store the result. This matrix requires O(m Γ n)
space. The input arrays rowSum
and colSum
are modified in-place and don't contribute additional space complexity beyond the input. No other significant auxiliary space is used. Therefore, the space complexity is O(m Γ n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Modifying Input Arrays In-Place
The most significant pitfall in this solution is that it modifies the input arrays rowSum
and colSum
directly. This can cause issues if:
- The caller expects the original arrays to remain unchanged
- The function needs to be called multiple times with the same input
- The input arrays are used elsewhere in the program after this function call
Example of the problem:
row_sums = [3, 8] col_sums = [4, 7] matrix = solution.restoreMatrix(row_sums, col_sums) # After execution: row_sums = [0, 0], col_sums = [0, 0] # Original values are lost!
Solution: Create copies of the input arrays before modifying them:
def restoreMatrix(self, rowSum: List[int], colSum: List[int]) -> List[List[int]]:
num_rows, num_cols = len(rowSum), len(colSum)
result_matrix = [[0] * num_cols for _ in range(num_rows)]
# Create copies to avoid modifying original arrays
row_remaining = rowSum.copy()
col_remaining = colSum.copy()
for row_idx in range(num_rows):
for col_idx in range(num_cols):
value_to_place = min(row_remaining[row_idx], col_remaining[col_idx])
result_matrix[row_idx][col_idx] = value_to_place
row_remaining[row_idx] -= value_to_place
col_remaining[col_idx] -= value_to_place
return result_matrix
2. Not Validating Input Consistency
While the problem guarantees valid input, in real-world scenarios, the sum of all row sums must equal the sum of all column sums for a valid solution to exist. Not checking this can lead to incorrect results or infinite loops in modified implementations.
Solution: Add validation if needed:
def restoreMatrix(self, rowSum: List[int], colSum: List[int]) -> List[List[int]]:
# Optional validation for production code
if sum(rowSum) != sum(colSum):
raise ValueError("No valid matrix exists: row sum total != column sum total")
# Rest of the implementation...
3. Assuming the Greedy Approach Always Works
While the greedy approach works perfectly for this problem, developers might incorrectly assume it works for all matrix reconstruction problems. Some variations (like requiring specific patterns or additional constraints) might need different approaches like backtracking or dynamic programming.
Key insight: The greedy approach works here because we only need to satisfy sum constraints with non-negative integers. Each local optimal choice (minimum value) guarantees progress toward a valid global solution without creating conflicts.
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Coding Interview 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
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 assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!