2392. Build a Matrix With Conditions - Explanation

Problem Link

Description

You are given a positive integer k. You are also given:

  • a 2D integer array rowConditions of size n where rowConditions[i] = [above[i], below[i]], and
  • a 2D integer array colConditions of size m where colConditions[i] = [left[i], right[i]].

The two arrays contain integers from 1 to k.

You have to build a k x k matrix that contains each of the numbers from 1 to k exactly once. The remaining cells should have the value 0.

The matrix should also satisfy the following conditions:

  • The number above[i] should appear in a row that is strictly above the row at which the number below[i] appears for all i from 0 to n - 1.

  • The number left[i] should appear in a column that is strictly left of the column at which the number right[i] appears for all i from 0 to m - 1.

Return any matrix that satisfies the conditions. If no answer exists, return an empty matrix.

Example 1:

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

Output: [[2,0,0],[0,0,1],[0,3,0]]

Example 2:

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

Output: []

Constraints:

  • 2 <= k <= 400
  • 1 <= rowConditions.length, colConditions.length <= 10,000
  • rowConditions[i].length == colConditions[i].length == 2
  • 1 <= above[i], below[i], left[i], right[i] <= k
  • above[i] != below[i]
  • left[i] != right[i]


Topics

Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Graph Representation - Building adjacency lists from edge lists to represent directed graphs
  • Topological Sort - Ordering nodes in a directed acyclic graph such that all edges point forward
  • Cycle Detection - Identifying cycles in directed graphs using DFS path tracking or in-degree analysis
  • DFS and BFS - Implementing depth-first and breadth-first traversals for graph algorithms

1. Topological Sort (DFS)

Intuition

The problem asks us to place numbers 1 to k in a k x k matrix such that certain numbers appear above others (row conditions) and certain numbers appear to the left of others (column conditions). This is essentially two independent ordering problems: one for rows and one for columns.

Each set of conditions forms a directed graph where an edge from A to B means A must come before B. Finding a valid ordering that satisfies all constraints is exactly what topological sort does. If there's a cycle in either graph, no valid ordering exists and we return an empty matrix.

Algorithm

  1. Build two directed graphs from rowConditions and colConditions.
  2. Perform topological sort on rowConditions using DFS:
    • Track visited nodes and nodes in the current path (to detect cycles).
    • If a cycle is detected (node appears in current path), return empty.
    • Add nodes to the order in reverse post-order, then reverse the result.
  3. Perform topological sort on colConditions similarly.
  4. Create mappings from each number to its row index (from row order) and column index (from column order).
  5. Place each number 1 to k at the position determined by these mappings.
  6. Return the constructed matrix.
class Solution:
    def buildMatrix(self, k: int, rowConditions: List[List[int]], colConditions: List[List[int]]) -> List[List[int]]:
        def dfs(src, adj, visit, path, order):
            if src in path:
                return False
            if src in visit:
                return True
            visit.add(src)
            path.add(src)
            for nei in adj[src]:
                if not dfs(nei, adj, visit, path, order):
                    return False
            path.remove(src)
            order.append(src)
            return True

        def topo_sort(edges):
            adj = defaultdict(list)
            for src, dst in edges:
                adj[src].append(dst)

            visit, path = set(), set()
            order = []
            for src in range(1, k + 1):
                if src not in visit:
                    if not dfs(src, adj, visit, path, order):
                        return []
            return order[::-1]

        row_order = topo_sort(rowConditions)
        if not row_order: return []

        col_order = topo_sort(colConditions)
        if not col_order: return []

        val_to_row = {num: i for i, num in enumerate(row_order)}
        val_to_col = {num: i for i, num in enumerate(col_order)}
        res = [[0] * k for _ in range(k)]
        for num in range(1, k + 1):
            r, c = val_to_row[num], val_to_col[num]
            res[r][c] = num

        return res

Time & Space Complexity

  • Time complexity: O(k2+n+m)O(k ^ 2 + n + m)
  • Space complexity:
    • O(k+n+m)O(k + n + m) extra space.
    • O(k2)O(k ^ 2) space for the output matrix.

Where nn is the size of the array rowConditionsrowConditions, mm is the size of the array colConditionscolConditions, and kk is the size of the output matrix.


2. Topological Sort (Kahn's Algorithm)

Intuition

This approach solves the same problem using Kahn's algorithm (BFS-based topological sort) instead of DFS. The key insight remains the same: we need valid orderings for both rows and columns. Kahn's algorithm processes nodes with zero incoming edges first, which naturally produces a valid topological order when no cycle exists.

If we process fewer than k nodes, it means there's a cycle in the graph and no valid ordering is possible.

Algorithm

  1. For each condition set (row and column), build an adjacency list and compute in-degrees for all nodes.
  2. Initialize a queue with all nodes that have zero in-degree.
  3. Process nodes from the queue:
    • Add the current node to the order.
    • Decrease the in-degree of all neighbors.
    • Add neighbors with zero in-degree to the queue.
  4. If the order contains fewer than k nodes, a cycle exists; return empty matrix.
  5. Create index mappings from the row and column orderings.
  6. Place each number at its determined (row, column) position in the matrix.
  7. Return the matrix.
class Solution:
    def buildMatrix(self, k: int, rowConditions: List[List[int]], colConditions: List[List[int]]) -> List[List[int]]:
        def topo_sort(edges):
            indegree = [0] * (k + 1)
            adj = [[] for _ in range(k + 1)]
            for u, v in edges:
                adj[u].append(v)
                indegree[v] += 1

            order = []
            q = deque()
            for i in range(1, k + 1):
                if not indegree[i]:
                    q.append(i)

            while q:
                node = q.popleft()
                order.append(node)
                for nei in adj[node]:
                    indegree[nei] -= 1
                    if not indegree[nei]:
                        q.append(nei)

            return order

        row_order = topo_sort(rowConditions)
        if len(row_order) != k: return []

        col_order = topo_sort(colConditions)
        if len(col_order) != k: return []

        res = [[0] * k for _ in range(k)]
        colIndex = [0] * (k + 1)
        for i in range(k):
            colIndex[col_order[i]] = i

        for i in range(k):
            res[i][colIndex[row_order[i]]] = row_order[i]
        return res

Time & Space Complexity

  • Time complexity: O(k2+n+m)O(k ^ 2 + n + m)
  • Space complexity:
    • O(k+n+m)O(k + n + m) extra space.
    • O(k2)O(k ^ 2) space for the output matrix.

Where nn is the size of the array rowConditionsrowConditions, mm is the size of the array colConditionscolConditions, and kk is the size of the output matrix.


Common Pitfalls

Treating Row and Column Conditions as One Graph

Row conditions and column conditions must be processed as two separate, independent graphs. Combining them into a single graph leads to incorrect cycle detection and wrong orderings since row ordering has no relation to column ordering.

Not Detecting Cycles Properly in DFS

When using DFS for topological sort, you need both a visited set (permanently processed nodes) and a path set (nodes in current recursion stack). Using only one set fails to detect back edges that indicate cycles.

# Wrong: single visited set misses cycles
if src in visited:
    return True  # Should check path set too

# Correct: check both sets
if src in path:
    return False  # Cycle detected
if src in visited:
    return True   # Already processed

Forgetting to Include Nodes Without Edges

Numbers from 1 to k that don't appear in any condition still need positions in the matrix. The topological sort must iterate over all values 1 to k, not just those present in the edge list.

Off-by-One Errors with 1-Indexed Values

The values are 1 to k (1-indexed) but arrays are typically 0-indexed. When building adjacency lists or index mappings, mixing these up causes index out of bounds errors or incorrect placements.