2392. Build a Matrix With Conditions - Explanation
Description
You are given a positive integer k. You are also given:
- a
2Dinteger arrayrowConditionsof sizenwhererowConditions[i] = [above[i], below[i]], and - a
2Dinteger arraycolConditionsof sizemwherecolConditions[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 numberbelow[i]appears for allifrom0ton - 1.The number
left[i]should appear in a column that is strictly left of the column at which the numberright[i]appears for allifrom0tom - 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 <= 4001 <= rowConditions.length, colConditions.length <= 10,000rowConditions[i].length == colConditions[i].length == 21 <= above[i], below[i], left[i], right[i] <= kabove[i] != below[i]left[i] != right[i]
Topics
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
- Build two directed graphs from
rowConditionsandcolConditions. - Perform topological sort on
rowConditionsusing 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.
- Perform topological sort on
colConditionssimilarly. - Create mappings from each number to its row index (from row order) and column index (from column order).
- Place each number
1tokat the position determined by these mappings. - 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 resTime & Space Complexity
- Time complexity:
- Space complexity:
- extra space.
- space for the output matrix.
Where is the size of the array , is the size of the array , and 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
- For each condition set (row and column), build an adjacency list and compute in-degrees for all nodes.
- Initialize a queue with all nodes that have zero in-degree.
- 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.
- If the order contains fewer than
knodes, a cycle exists; return empty matrix. - Create index mappings from the row and column orderings.
- Place each number at its determined (row, column) position in the matrix.
- 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 resTime & Space Complexity
- Time complexity:
- Space complexity:
- extra space.
- space for the output matrix.
Where is the size of the array , is the size of the array , and 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 processedForgetting 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.
Sign in to join the discussion