1605. Find Valid Matrix Given Row and Column Sums
1605. Find Valid Matrix Given Row and Column Sums
Problem Statement
You are given two arrays rowSum and colSum of non-negative integers where rowSum[i] is the sum of the elements in the i-th row and colSum[j] is the sum of the elements in the j-th column of a 2D matrix. In other words, you do not know the elements of the matrix, but you do know the sums of each row and column.
Find any non-negative integer matrix of size rowSum.length x colSum.length that satisfies the rowSum and colSum requirements.
Return a 2D array representing any matrix that fulfills the requirements. It’s guaranteed that at least one matrix that fulfills the requirements exists.
Examples
Example 1:
Input: rowSum = [3,8], colSum = [4,7]
Output: [[3,0],
[1,7]]
Explanation:
0th row: 3 + 0 = 3 == rowSum[0]
1st row: 1 + 7 = 8 == rowSum[1]
0th column: 3 + 1 = 4 == colSum[0]
1st column: 0 + 7 = 7 == colSum[1]
The matrix is valid.
Example 2:
Input: rowSum = [5,7,10], colSum = [8,6,8]
Output: [[5,0,0],
[3,4,0],
[0,2,8]]
Example 3:
Input: rowSum = [14,9], colSum = [6,9,8]
Output: [[6,8,0],
[0,1,8]]
Constraints
1 <= rowSum.length, colSum.length <= 5000 <= rowSum[i], colSum[j] <= 10^8sum(rowSum) == sum(colSum)
Clarification Questions
Before diving into the solution, here are 5 important clarifications and assumptions to discuss during an interview:
-
Matrix construction: What are the constraints for matrix values? (Assumption: All values must be non-negative integers - cannot have negative values)
-
Sum matching: Must row and column sums match exactly? (Assumption: Yes - sum of each row must equal rowSum[i], sum of each column must equal colSum[j])
-
Matrix uniqueness: Is there a unique solution? (Assumption: No - multiple valid matrices exist, return any valid one)
-
Matrix size: What is the matrix size? (Assumption: m x n where m = rowSum.length, n = colSum.length)
-
Return format: What should we return? (Assumption: Any valid matrix - 2D array satisfying row and column sum constraints)
Interview Deduction Process (20 minutes)
Step 1: Brute-Force Approach (5 minutes)
Initial Thought: “I need to construct matrix. Let me try all possible values for each cell.”
Naive Solution: Try all possible values for each matrix cell, check if row and column sums match.
Complexity: Exponential time, O(m × n) space
Issues:
- Exponential time complexity
- Tries many invalid matrices
- Very inefficient
- Doesn’t leverage greedy property
Step 2: Semi-Optimized Approach (7 minutes)
Insight: “I can use greedy approach - fill cells one by one, satisfying constraints.”
Improved Solution: Fill matrix greedily. For each cell (i, j), set value to min(rowSum[i], colSum[j]). Update remaining row and column sums.
Complexity: O(m × n) time, O(m × n) space
Improvements:
- Greedy approach is correct
- O(m × n) time is optimal
- Handles all cases correctly
- Simple and efficient
Step 3: Optimized Solution (8 minutes)
Final Optimization: “Greedy approach is optimal. Fill cells row by row or column by column.”
Best Solution: Greedy approach is optimal. Fill matrix row by row. For each cell, set to min(remaining rowSum, remaining colSum). Update both sums.
Complexity: O(m × n) time, O(m × n) space
Key Realizations:
- Greedy approach works perfectly
- O(m × n) time is optimal - must fill each cell
- O(m × n) space is necessary for result matrix
- Min of remaining sums ensures validity
Solution Approach
This is a greedy algorithm problem. The key insight is to fill the matrix cell by cell, always placing the minimum of the remaining row sum and column sum at each position. This ensures we satisfy both constraints while making progress.
Key Insights:
- Greedy Choice: At each cell
(i, j), placemin(rowSum[i], colSum[j])- This satisfies both constraints as much as possible
- We can always place a non-negative value (guaranteed by constraints)
- Two Pointers: Use two pointers
iandjto track current row and column- Advance row pointer when
rowSum[i] == 0(row is satisfied) - Advance column pointer when
colSum[j] == 0(column is satisfied)
- Advance row pointer when
- Optimal: This greedy strategy always finds a valid matrix
Algorithm:
- Initialize: Create matrix of zeros, initialize pointers
i = 0,j = 0 - Fill Cell: Place
min(rowSum[i], colSum[j])atmatrix[i][j] - Update Sums: Subtract placed value from both
rowSum[i]andcolSum[j] - Advance Pointers: Move to next row/column when sum becomes 0
- Repeat: Continue until all rows and columns are satisfied
Solution
Solution: Greedy with Two Pointers
class Solution {
public:
vector<vector<int>> restoreMatrix(vector<int>& rowSum, vector<int>& colSum) {
const int N = rowSum.size(), M = colSum.size();
vector<vector<int>> matrix(N, vector<int>(M, 0));
int i = 0, j = 0;
while(i < N && j < M) {
int v = min(rowSum[i], colSum[j]);
matrix[i][j] = v;
rowSum[i] -= v;
colSum[j] -= v;
if(rowSum[i] == 0) {
i++;
}
if(colSum[j] == 0) {
j++;
}
}
return matrix;
}
};
Algorithm Explanation:
- Initialize (Lines 4-6):
N: Number of rows (rowSum.size())M: Number of columns (colSum.size())matrix: Initialize with zerosi,j: Pointers for current row and column (start at 0)
- Greedy Fill (Lines 7-16):
- While both pointers are valid (
i < N && j < M):- Calculate value:
v = min(rowSum[i], colSum[j])- Place the minimum to satisfy both constraints
- Place value:
matrix[i][j] = v - Update sums: Subtract
vfrom bothrowSum[i]andcolSum[j] - Advance pointers:
- If
rowSum[i] == 0: Move to next row (i++) - If
colSum[j] == 0: Move to next column (j++)
- If
- Calculate value:
- While both pointers are valid (
- Return (Line 17):
- Return the constructed matrix
Why This Works:
Key Insight: At each step, we place the maximum value that satisfies both the row and column constraints.
Strategy:
- Place Minimum:
min(rowSum[i], colSum[j])ensures:- We don’t exceed either constraint
- We make maximum progress (satisfy at least one constraint completely)
- Update Sums: Subtracting the placed value maintains the remaining sums
- Advance Pointers: When a sum becomes 0, that row/column is satisfied, so we move on
- Termination: The loop ends when all rows or all columns are satisfied
- Since
sum(rowSum) == sum(colSum), when we finish, both are satisfied
- Since
Example Walkthrough:
Example 1: rowSum = [3,8], colSum = [4,7]
Initial state:
rowSum = [3, 8]
colSum = [4, 7]
matrix = [[0, 0],
[0, 0]]
i = 0, j = 0
Execution:
Step 1: i=0, j=0
v = min(3, 4) = 3
matrix[0][0] = 3
rowSum[0] = 3 - 3 = 0 → i++
colSum[0] = 4 - 3 = 1
State: i=1, j=0, rowSum=[0,8], colSum=[1,7]
Step 2: i=1, j=0
v = min(8, 1) = 1
matrix[1][0] = 1
rowSum[1] = 8 - 1 = 7
colSum[0] = 1 - 1 = 0 → j++
State: i=1, j=1, rowSum=[0,7], colSum=[0,7]
Step 3: i=1, j=1
v = min(7, 7) = 7
matrix[1][1] = 7
rowSum[1] = 7 - 7 = 0 → i++
colSum[1] = 7 - 7 = 0 → j++
State: i=2, j=2 (both out of bounds)
Loop ends: i=2 >= N=2
Final matrix:
[[3, 0],
[1, 7]]
Verification:
Row 0: 3 + 0 = 3 ✓
Row 1: 1 + 7 = 8 ✓
Col 0: 3 + 1 = 4 ✓
Col 1: 0 + 7 = 7 ✓
Example 2: rowSum = [5,7,10], colSum = [8,6,8]
Execution:
Step 1: i=0, j=0
v = min(5, 8) = 5
matrix[0][0] = 5
rowSum[0] = 0 → i++
colSum[0] = 3
State: i=1, j=0
Step 2: i=1, j=0
v = min(7, 3) = 3
matrix[1][0] = 3
rowSum[1] = 4
colSum[0] = 0 → j++
State: i=1, j=1
Step 3: i=1, j=1
v = min(4, 6) = 4
matrix[1][1] = 4
rowSum[1] = 0 → i++
colSum[1] = 2
State: i=2, j=1
Step 4: i=2, j=1
v = min(10, 2) = 2
matrix[2][1] = 2
rowSum[2] = 8
colSum[1] = 0 → j++
State: i=2, j=2
Step 5: i=2, j=2
v = min(8, 8) = 8
matrix[2][2] = 8
rowSum[2] = 0 → i++
colSum[2] = 0 → j++
State: i=3, j=3 (both out of bounds)
Final matrix:
[[5, 0, 0],
[3, 4, 0],
[0, 2, 8]]
Algorithm Breakdown
Why Greedy Works
Greedy Choice Property: At each step, placing min(rowSum[i], colSum[j]) is optimal because:
- Satisfies Constraints: Doesn’t exceed either row or column sum
- Maximizes Progress: Satisfies at least one constraint completely
- No Regret: Any value we place here reduces both constraints, so we want to place as much as possible
Optimal Substructure: After placing a value, the remaining problem (satisfying remaining sums) is independent and can be solved optimally.
Why Two Pointers Work
Pointer Advancement:
- When
rowSum[i] == 0: Rowiis satisfied, move to next row - When
colSum[j] == 0: Columnjis satisfied, move to next column - Both can advance in the same iteration if both become 0
Termination: The loop ends when i >= N or j >= M. Since sum(rowSum) == sum(colSum), when we finish processing all cells, both row and column sums are satisfied.
Mathematical Guarantee
Invariant: At any point, sum(rowSum) == sum(colSum) (remaining sums are equal).
Proof:
- Initially:
sum(rowSum) == sum(colSum)(given) - At each step: We subtract the same value
vfrom bothrowSum[i]andcolSum[j] - Therefore: The sums remain equal throughout
Corollary: When all rows are satisfied (i >= N), all columns are also satisfied, and vice versa.
Time & Space Complexity
- Time Complexity: O(N × M) where N is number of rows, M is number of columns
- In worst case, we visit each cell once
- Each iteration does O(1) work
- Total: O(N × M)
- Space Complexity: O(N × M)
- Space for the output matrix
- O(1) extra space for variables
Key Points
- Greedy Choice: Always place
min(rowSum[i], colSum[j])at each cell - Two Pointers: Use pointers to track current row and column
- Advance When Zero: Move to next row/column when sum becomes 0
- Optimal: This greedy strategy always finds a valid matrix
- Simple: Straightforward implementation
Alternative Approaches
Approach 1: Greedy with Two Pointers (Current Solution)
- Time: O(N × M)
- Space: O(N × M)
- Best for: Optimal solution, most efficient
Approach 2: Fill Row by Row
- Time: O(N × M)
- Space: O(N × M)
- Similar: Fill each row completely before moving to next
- Less efficient: May need to backtrack or adjust
Approach 3: Fill Column by Column
- Time: O(N × M)
- Space: O(N × M)
- Similar: Fill each column completely before moving to next
- Less efficient: May need to backtrack or adjust
Edge Cases
- Single cell:
rowSum = [5],colSum = [5]→[[5]] - Single row:
rowSum = [10],colSum = [3,7]→[[3,7]] - Single column:
rowSum = [3,7],colSum = [10]→[[3],[7]] - All zeros:
rowSum = [0,0],colSum = [0,0]→[[0,0],[0,0]] - Large values: Works with values up to 10^8
Common Mistakes
- Wrong minimum: Not using
min(rowSum[i], colSum[j]) - Not updating sums: Forgetting to subtract placed value
- Wrong pointer logic: Not advancing pointers correctly
- Index errors: Incorrect matrix indexing
- Not handling zeros: Not advancing when sum becomes 0
Related Problems
- 406. Queue Reconstruction by Height - Greedy with constraints
- 435. Non-overlapping Intervals - Greedy interval selection
- 1029. Two City Scheduling - Greedy with sorting
- 1710. Maximum Units on a Truck - Greedy selection
Follow-Up: Why Minimum is Optimal
Question: Why does placing min(rowSum[i], colSum[j]) give an optimal solution?
Answer:
- Maximizes Progress: By placing the maximum possible value (minimum of constraints), we satisfy at least one constraint completely
- No Waste: We don’t place more than needed, leaving room for other cells
- Greedy Choice: This locally optimal choice leads to a globally optimal solution
- Mathematical Proof: Any other value would either:
- Exceed a constraint (invalid)
- Leave more work for later (suboptimal)
- Be equivalent (same result)
Tags
Array, Matrix, Greedy, Medium