Taro Logo

Find Valid Matrix Given Row and Column Sums

Medium
Asked by:
Profile picture
18 views
Topics:
ArraysGreedy Algorithms

You are given two arrays rowSum and colSum of non-negative integers where rowSum[i] is the sum of the elements in the ith row and colSum[j] is the sum of the elements of the jth 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 matrix of non-negative integers 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.

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 row and column sums match, and all matrix elements are non-negative.
Another possible matrix is: [[1,2],
                             [3,5]]

Example 2:

Input: rowSum = [5,7,10], colSum = [8,6,8]
Output: [[0,5,0],
         [6,1,0],
         [2,0,8]]

Constraints:

  • 1 <= rowSum.length, colSum.length <= 500
  • 0 <= rowSum[i], colSum[i] <= 108
  • sum(rowSum) == sum(colSum)

Solution


Clarifying Questions

When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:

  1. What are the possible ranges for the values in the `rowSum` and `colSum` arrays? Can they be zero or negative?
  2. Is it guaranteed that the sum of the `rowSum` array is equal to the sum of the `colSum` array? What should I return if this condition is not met?
  3. If multiple valid matrices exist, is any valid matrix acceptable, or is there a specific criteria for choosing which one to return?
  4. What are the dimensions (number of rows and columns) implied by the lengths of `rowSum` and `colSum`? Should I assume `rowSum.length` is the number of rows and `colSum.length` is the number of columns?
  5. If a valid matrix cannot be constructed from the given `rowSum` and `colSum`, what should the function return? (e.g., null, an empty matrix, or an exception?)

Brute Force Solution

Approach

The brute force method tries every possible combination to fill the matrix. We systematically explore all potential values for each cell, making sure row and column sum constraints are met at the end. If a combination does not satisfy all the criteria, it's discarded.

Here's how the algorithm would work step-by-step:

  1. Start with a completely empty matrix, where every cell has a value of zero.
  2. Begin filling the matrix one cell at a time, starting from the top left.
  3. For each cell, try every possible numerical value, starting from zero, until a maximum value that makes sense given the row and column sums.
  4. After filling each cell, check if the current sums of the row and column that cell belongs to exceed the given row and column sums.
  5. If the sums exceed the given totals, try a smaller value for the current cell.
  6. If the sums do not exceed totals continue until all cells are assigned a value.
  7. Once the matrix is completely filled, calculate the actual sums of each row and each column.
  8. Compare these actual sums with the required row and column sums that were provided.
  9. If all row sums and column sums match exactly, you have found a valid matrix. If they don't match exactly, discard the entire matrix and try a different combination of cell values.
  10. Repeat steps 3-9 until a matching matrix is found, or all possible value combinations have been exhausted.

Code Implementation

def find_valid_matrix_brute_force(row_sums, col_sums):
    number_of_rows = len(row_sums)
    number_of_columns = len(col_sums)

    matrix = [[0] * number_of_columns for _ in range(number_of_rows)]

    def backtrack(row_index, col_index):
        if row_index == number_of_rows:
            #Check matrix validity
            actual_row_sums = [sum(row) for row in matrix]
            actual_col_sums = [sum(matrix[i][j] for i in range(number_of_rows)) for j in range(number_of_columns)]

            if actual_row_sums == row_sums and actual_col_sums == col_sums:
                return True
            else:
                return False

        next_row_index = row_index
        next_col_index = col_index + 1
        if next_col_index == number_of_columns:
            next_row_index = row_index + 1
            next_col_index = 0

        # Try every possible value for the current cell
        for possible_value in range(min(row_sums[row_index], col_sums[col_index]) + 1):
            matrix[row_index][col_index] = possible_value

            # Recursively try to fill the rest of the matrix
            if backtrack(next_row_index, next_col_index):
                return True

        # Reset the cell for backtracking purposes
        matrix[row_index][col_index] = 0
        return False

    if backtrack(0, 0):
        return matrix
    else:
        return []

Big(O) Analysis

Time Complexity
O(m^(m*n))The algorithm explores all possible combinations for filling an m x n matrix. For each cell, we are trying values from 0 up to potentially the maximum row or column sum (let's denote this maximum value as 'm'). Therefore, for each of the m*n cells, we may iterate up to 'm' times. This leads to a time complexity that is exponential and related to the number of possible matrix configurations, approximating O(m^(m*n)). This brute force is very inefficient and would only work for the smallest of inputs because for each possible combination, the row and column sums are checked which add to the overhead.
Space Complexity
O(m * n)The brute force approach described requires storing the matrix itself, which has dimensions m x n, where m is the number of rows and n is the number of columns. This matrix represents the primary auxiliary space used. No other significant auxiliary data structures are mentioned in the description. Therefore, the space complexity is directly proportional to the size of the matrix, resulting in O(m * n). We can also represent it as O(N) where N is the number of cells in the matrix.

Optimal Solution

Approach

The core idea is to fulfill the row and column sum requirements one cell at a time in a greedy fashion. We iteratively find the smallest requirement of the current row and column and assign that value to the corresponding cell, effectively satisfying either the row or column constraint (or both) and reducing the problem size.

Here's how the algorithm would work step-by-step:

  1. Imagine building the matrix cell by cell, starting from the top left.
  2. For each cell, look at the remaining sum required for its row and its column.
  3. Choose the smaller of these two sums and put that number in the cell. This ensures you don't exceed either the row or column's requirement.
  4. Update the remaining row and column sums by subtracting the number you just put in the cell.
  5. Move to the next cell (either across the row or down the column, it doesn't matter) and repeat the process.
  6. Continue until you've filled all the cells in the matrix. Because you always picked the smallest possible value for each cell, you're guaranteed to meet all row and column sum requirements.

Code Implementation

def find_valid_matrix(row_sums, column_sums):
    number_of_rows = len(row_sums)
    number_of_columns = len(column_sums)
    result_matrix = [[0] * number_of_columns for _ in range(number_of_rows)]

    for row_index in range(number_of_rows):
        for column_index in range(number_of_columns):
            # We take the minimum to satisfy at least one constraint.
            current_value = min(row_sums[row_index], column_sums[column_index])
            result_matrix[row_index][column_index] = current_value

            # Update the row and column sums after assigning the value.
            row_sums[row_index] -= current_value

            column_sums[column_index] -= current_value

    return result_matrix

Big(O) Analysis

Time Complexity
O(m*n)The algorithm iterates through each cell of the matrix to determine its value. The matrix has dimensions m x n, where m is the number of rows and n is the number of columns, corresponding to the lengths of rowSum and colSum respectively. For each of the m*n cells, we perform a constant time operation to determine the minimum of the row and column sums and update the row and column sums. Therefore, the overall time complexity is proportional to the number of cells in the matrix, leading to a time complexity of O(m*n).
Space Complexity
O(m * n)The algorithm constructs an m x n matrix to store the result. Although the input consists of row and column sums, the primary space usage comes from creating this output matrix. Therefore, the auxiliary space required is directly proportional to the number of cells in the matrix, where m is the number of rows and n is the number of columns. Consequently, the space complexity is O(m * n).

Edge Cases

Empty rowSum or colSum arrays
How to Handle:
Return an empty matrix (or null) as there's no valid output possible.
rowSum and colSum have different total sums
How to Handle:
Return an empty matrix (or null) because a valid matrix cannot be constructed.
Single row and single column (1x1 matrix)
How to Handle:
Create a 1x1 matrix where the single element is the value from both rowSum[0] and colSum[0] (which must be equal).
rowSum or colSum contains negative numbers
How to Handle:
The problem states non-negative integers only, so return an error or IllegalArgumentException.
Large rowSum and colSum values leading to integer overflow
How to Handle:
Use a larger integer type (e.g., long) to prevent overflow during assignment to the matrix.
All rowSum and colSum values are zero
How to Handle:
Create a matrix filled with zeros.
Only one rowSum or colSum is non-zero while the others are zero
How to Handle:
Allocate all the value to one cell on that row or colum.
Multiple valid solutions exist
How to Handle:
The algorithm should produce *a* valid solution; any valid solution is acceptable.