Build a Matrix With Conditions

Updated on 20 May, 2025
Build a Matrix With Conditions header image

Problem Statement

In this task, you are provided with:

  1. An integer k, representing the size of a square matrix (k x k).
  2. Two sets of conditions in the form of 2D arrays: rowConditions and colConditions.

Each entry in rowConditions is a pair [abovei, belowi], indicating that the number abovei should be placed in a row above the row where belowi is located. Similarly, each entry in colConditions is a pair [lefti, righti], specifying that the number lefti should be located in a column to the left of the column where righti is found.

Your goal is to fill out the k x k matrix following these rules:

  • Every number from 1 to k is used exactly once in the matrix.
  • Any unfilled cells in the matrix should be assigned the value 0.

The matrix must adhere to all the specific rowConditions and colConditions. If a valid arrangement exists, return the matrix; otherwise, if no such arrangement is possible, you should return an empty matrix.

Examples

Example 1

Input:

k = 3, rowConditions = [[1,2],[3,2]], colConditions = [[2,1],[3,2]]

Output:

[[3,0,0],[0,0,1],[0,2,0]]

Explanation:

The diagram above shows a valid example of a matrix that satisfies all the conditions.
The row conditions are the following:
- Number 1 is in row 1, and number 2 is in row 2, so 1 is above 2 in the matrix.
- Number 3 is in row 0, and number 2 is in row 2, so 3 is above 2 in the matrix.
The column conditions are the following:
- Number 2 is in column 1, and number 1 is in column 2, so 2 is left of 1 in the matrix.
- Number 3 is in column 0, and number 2 is in column 1, so 3 is left of 2 in the matrix.
Note that there may be multiple correct answers.

Example 2

Input:

k = 3, rowConditions = [[1,2],[2,3],[3,1],[2,3]], colConditions = [[2,1]]

Output:

[]

Explanation:

From the first two conditions, 3 has to be below 1 but the third conditions needs 3 to be above 1 to be satisfied.
No matrix can satisfy all the conditions, so we return the empty matrix.

Constraints

  • 2 <= k <= 400
  • 1 <= rowConditions.length, colConditions.length <= 104
  • rowConditions[i].length == colConditions[i].length == 2
  • 1 <= abovei, belowi, lefti, righti <= k
  • abovei != belowi
  • lefti != righti

Approach and Intuition

The problem can be perceived as finding a valid topological sort for both rows and columns given the constraints, and then mapping these sorted orders back to a matrix format. Here’s how you can think about the process:

  1. Model Row and Column Constraints as Graphs:

    • For rowConditions, create an adjacency list where each directed edge u -> v indicates u is to be above v.
    • Similarly, for colConditions, form another adjacency structure where u -> v now signifies u is to the left of v.
  2. Topological Sorting:

    • With the built graph structures, perform a topological sort. If the sort is successful, it provides a valid order.
    • If a cycle exists in any graph (detected during topological sort), it implies conflicting conditions, thus making it impossible to build a valid matrix, in which case return an empty matrix.
  3. Mapping Numbers to the Matrix:

    • Using the orders obtained from the topological sorting (for both rows and columns), start placing numbers at their appropriate row and column indices.
    • Fill non-assigned cells of the matrix with 0.
  4. Edge Cases:

    • Handle cases where multiple valid matrices might exist. Any valid matrix as per the conditions should be acceptable.
    • Ensure the solution is efficient to handle the upper constraint limits, considering k could be as large as 400 and conditions can be up to 20,000.

This overall approach leverages graph theory concepts, especially topological sorting, to decide the relative positioning of numbers in the matrix, making the problem much more structured and manageable.

Solutions

  • C++
  • Java
  • Python
cpp
class Solution {
public:
    vector<vector<int>> generateMatrix(int size, vector<vector<int>>& rowReqs, vector<vector<int>>& colReqs) {
        vector<int> rowOrder = topologicalSort(rowReqs, size);
        vector<int> columnOrder = topologicalSort(colReqs, size);

        if (rowOrder.empty() || columnOrder.empty()) return {};

        vector<vector<int>> result(size, vector<int>(size, 0));
        for (int row = 0; row < size; row++) {
            for (int col = 0; col < size; col++) {
                if (rowOrder[row] == columnOrder[col]) {
                    result[row][col] = rowOrder[row];
                }
            }
        }
        return result;
    }

private:
    vector<int> topologicalSort(vector<vector<int>>& relations, int nodes) {
        vector<vector<int>> adjacencyList(nodes + 1);
        vector<int> inDegree(nodes + 1), sortedResult;
        for (auto relation : relations) {
            adjacencyList[relation[0]].push_back(relation[1]);
            inDegree[relation[1]]++;
        }
        queue<int> zeroInDegree;
        for (int node = 1; node <= nodes; node++) {
            if (inDegree[node] == 0) zeroInDegree.push(node);
        }
        while (!zeroInDegree.empty()) {
            int current = zeroInDegree.front();
            zeroInDegree.pop();
            sortedResult.push_back(current);
            nodes--;
            for (int next : adjacencyList[current]) {
                if (--inDegree[next] == 0) zeroInDegree.push(next);
            }
        }
        if (nodes != 0) return {};
        return sortedResult;
    }
};

This C++ solution outlines a strategy to construct a matrix based on specified row and column requirements using a topological sort approach. The implementation comprises of the main function generateMatrix and a helper function topologicalSort. Here’s the breakdown of the code workflow:

  • Topological Sorting Function (topologicalSort):

    • The function processes dependencies (precedence constraints) represented as a vector of vectors, relations, and determines a valid order.
    • Constructs an adjacency list and an in-degree array to keep track of dependencies.
    • Employs a queue to manage nodes with zero in-degree, facilitating the topological sort as nodes are processed when their dependencies are resolved.
    • If all nodes are processed (indicated by the nodes counter reaching zero), it returns the sort order; otherwise, it returns an empty vector indicating an error or cyclic dependency.
  • Matrix Generation (generateMatrix):

    • Accepts size of the matrix and requirement vectors for both rows and columns.
    • Calls topologicalSort for both rowReqs and colReqs to get the valid orders.
    • Initializes the matrix and sets the element at position (row, col) following the orders obtained from the topological sorts. Ensures that the conditions specified by row and column requirements are met.
    • Elements are added to the result matrix based on comparisons of sorted row and column orders.

Key Aspects of Functionality:

  • Handles dependencies and conflicts in matrix construction carefully using topological sorting to ensure the generated matrix adheres to the conditions.
  • Utilizes the adjacency list and in-degree mechanism smartly to dynamically update and sort nodes, which is efficient in managing complex dependencies.
  • Returns an empty matrix if it's not possible to meet row or column requirements due to cyclic dependencies or other issues, providing fail-safe behavior.

The approach assumes valid input ranges and does not employ error handling for unexpected input types, which should be considered in production-level code. The solution effectively combines matrix manipulation with graph theory concepts to address the problem of constructing a matrix under specific conditions.

java
public class MatrixBuilder {

    public int[][] constructMatrix(
        int size,
        int[][] rowRules,
        int[][] columnRules
    ) {
        int[] rowOrder = performTopoSort(rowRules, size);
        int[] columnOrder = performTopoSort(columnRules, size);
        if (
            rowOrder.length == 0 || columnOrder.length == 0
        ) return new int[0][0];
        int[][] resultMatrix = new int[size][size];
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                if (rowOrder[i] == columnOrder[j]) {
                    resultMatrix[i][j] = rowOrder[i];
                }
            }
        }
        return resultMatrix;
    }

    private int[] performTopoSort(int[][] relations, int elementsCount) {
        List<Integer>[] graph = new ArrayList[elementsCount + 1];
        for (int i = 0; i <= elementsCount; i++) {
            graph[i] = new ArrayList<Integer>();
        }
        int[] inDegree = new int[elementsCount + 1], sortedElements = new int[elementsCount];
        int idx = 0;
        for (int[] edge : relations) {
            graph[edge[0]].add(edge[1]);
            inDegree[edge[1]]++;
        }
        Queue<Integer> queue = new LinkedList<>();
        for (int i = 1; i <= elementsCount; i++) {
            if (inDegree[i] == 0) queue.add(i);
        }
        while (!queue.isEmpty()) {
            int current = queue.poll();
            sortedElements[idx++] = current;
            elementsCount--;
            for (int connectedNode : graph[current]) {
                if (--inDegree[connectedNode] == 0) queue.add(connectedNode);
            }
        }
        if (elementsCount != 0) return new int[0];
        return sortedElements;
    }
}

Below, find the explanation of how to build a matrix based on given row and column conditions using Java:

  • Start by defining a MatrixBuilder class that includes the method constructMatrix. This method takes three parameters: size, rowRules, and columnRules.
  • Inside constructMatrix, invoke the performTopoSort method for both rowRules and columnRules to obtain a sorted order of row and column indices respectively.
  • If either sorting results in an empty array, return a zero-sized matrix indicating that a valid topological order does not exist due to cyclic dependencies.
  • Initialize a matrix resultMatrix of dimensions size x size. Fill this matrix by checking condition where sorted row index and column index match (rowOrder[i] == columnOrder[j]). If true, place the sorted index value in the matrix.
  • The private method performTopoSort is responsible for performing topological sorting on provided rules (relations between nodes). Here's the breakdown of this method:
    • Initialize an adjacency list graph and an array inDegree to track the number of incoming edges for each node. Similarly, sortedElements will store the topologically sorted elements.
    • Update the graph and the inDegree array using the given relation of nodes (rowRules or columnRules).
    • Employ a queue mechanism to sort nodes level by level. Nodes with zero incoming edges are processed first.
    • Adding nodes to the result and adjusting inDegree for connected nodes ensures that each node is processed when all its prerequisites are met.
    • If, after processing all nodes, there remain unprocessed nodes (elementsCount not zero), this indicates cyclic dependencies, returning an empty array.
  • Return the sorted order from performTopoSort to constructMatrix.
  • This approach efficiently constructs the required matrix while ensuring that all provided conditions (rules for rows and columns) are satisfied, using topological sorting to respect precedence constraints in the matrix construction.
python
class Solution:
    def constructMatrix(self, dimension: int, row_rules: List[List[int]], column_rules: List[List[int]]) -> List[List[int]]:
        row_order = self._topologicalSort(row_rules, dimension)
        column_order = self._topologicalSort(column_rules, dimension)
        if not row_order or not column_order:
            return []
        grid = [[0] * dimension for _ in range(dimension)]
        for row in range(dimension):
            for col in range(dimension):
                if row_order[row] == column_order[col]:
                    grid[row][col] = row_order[row]
        return grid

    def _topologicalSort(self, relations, size):
        adjacency_list = [[] for _ in range(size + 1)]
        in_degree = [0] * (size + 1)
        sorted_list = []
        for edge in relations:
            adjacency_list[edge[0]].append(edge[1])
            in_degree[edge[1]] += 1
        zero_degree_queue = deque()
        for node in range(1, size + 1):
            if in_degree[node] == 0:
                zero_degree_queue.append(node)
        while zero_degree_queue:
            node = zero_degree_queue.popleft()
            sorted_list.append(node)
            size -= 1
            for neighbor in adjacency_list[node]:
                in_degree[neighbor] -= 1
                if in_degree[neighbor] == 0:
                    zero_degree_queue.append(neighbor)
        if size != 0:
            return []
        return sorted_list

The presented Python code defines a Solution class tasked with constructing a matrix based on specific row and column ordering rules. The constructMatrix method is designed to create a matrix of a specified dimension, adhering to given constraints from row_rules and column_rules. Here's how it operates:

  • First, it utilizes a helper method _topologicalSort to determine the order of rows and columns based on the provided rules, conceptualized as directed graph topological sorting.
  • If either sorting results in an empty order, indicating an impossible arrangement due to circular dependencies or other reasons, it returns an empty matrix.
  • Then, it initializes a matrix filled with zeros. For each permissible position determined by the sorted row and column orders, it assigns the values at respective positions if the row and column indices match.
  • The _topologicalSort method constructs an adjacency list and calculates in-degrees for each node (rule) based on the relations defined in rules. It processes nodes in topological order using a queue (Kahn's algorithm), continuously adjusting in-degrees and queuing nodes with zero in-degrees, then checks for remaining unprocessed nodes, which would indicate a cycle or conflict in rules.

This approach effectively handles constraints for matrix construction using topological sorting, ensuring that the sequence of placement adheres strictly to the rules, or signifies an error by returning an empty array if the rules can't be satisfied. This technique is especially useful for tasks involving precedence or rule-based structuring in multidimensional arrays.

Comments

No comments yet.