Build a Matrix With Conditions
Description
You are given a positive integer k. You are also given two 2D integer arrays:
rowConditionsof sizen, where eachrowConditions[i] = [above_i, below_i]means the numberabove_imust appear in a row that is strictly above the row containingbelow_i.colConditionsof sizem, where eachcolConditions[i] = [left_i, right_i]means the numberleft_imust appear in a column that is strictly to the left of the column containingright_i.
Both arrays contain integers from 1 to k.
You need to build a k x k matrix that places each number from 1 to k exactly once. All remaining cells should be filled with 0.
Return any matrix that satisfies all the row and column ordering conditions. If no valid arrangement exists, 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: One valid placement is:
- Row ordering: 3 in row 0, 1 in row 1, 2 in row 2. This satisfies: 1 is above 2 (row 1 < row 2) and 3 is above 2 (row 0 < row 2).
- Column ordering: 3 in column 0, 2 in column 1, 1 in column 2. This satisfies: 2 is left of 1 (col 1 < col 2) and 3 is left of 2 (col 0 < col 1).
Note 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: The row conditions create a cycle: 1 must be above 2, 2 must be above 3, and 3 must be above 1. This is a circular dependency — no valid row ordering exists, so we return an empty matrix.
Constraints
- 2 ≤ k ≤ 400
- 1 ≤ rowConditions.length, colConditions.length ≤ 10^4
- 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
Editorial
Brute Force - Try All Permutations
Intuition
The most straightforward way to think about this problem is: each number from 1 to k needs a unique row and a unique column. So we need to assign a row position to every number and a column position to every number.
Imagine you have k numbered cards and a k×k grid. You try placing card 1 somewhere, card 2 somewhere else, and so on. After placing all cards, you check whether every row constraint ("this card must be in a higher row than that card") and every column constraint ("this card must be in a more leftward column than that card") is satisfied.
The brute force approach generates all possible row orderings (permutations of 1 to k) and all possible column orderings. For each combination of a row permutation and a column permutation, we verify all the constraints. If a valid combination is found, we build the matrix.
Step-by-Step Explanation
Let's trace through a small example: k = 3, rowConditions = [[1,2],[3,2]], colConditions = [[2,1],[3,2]].
Step 1: Generate all permutations of [1, 2, 3] for row ordering. There are 3! = 6 permutations: [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1].
Step 2: Take the first row permutation: [1, 2, 3]. This means number 1 is in row 0, number 2 is in row 1, number 3 is in row 2.
Step 3: Check row constraints against this ordering:
- Constraint [1,2]: Is 1 above 2? Number 1 is in row 0, number 2 is in row 1. 0 < 1, so YES.
- Constraint [3,2]: Is 3 above 2? Number 3 is in row 2, number 2 is in row 1. 2 < 1? NO! This fails.
Step 4: Row permutation [1, 2, 3] is invalid. Try next: [1, 3, 2]. Number 1 → row 0, number 3 → row 1, number 2 → row 2.
- Constraint [1,2]: 1 in row 0, 2 in row 2. 0 < 2? YES.
- Constraint [3,2]: 3 in row 1, 2 in row 2. 1 < 2? YES.
Step 5: Row permutation [1, 3, 2] passes! Now try all column permutations. Take [2, 3, 1]: number 2 → col 0, number 3 → col 1, number 1 → col 2.
- Constraint [2,1]: 2 in col 0, 1 in col 2. 0 < 2? YES.
- Constraint [3,2]: 3 in col 1, 2 in col 0. 1 < 0? NO! Fails.
Step 6: Try column permutation [3, 2, 1]: number 3 → col 0, number 2 → col 1, number 1 → col 2.
- Constraint [2,1]: 2 in col 1, 1 in col 2. 1 < 2? YES.
- Constraint [3,2]: 3 in col 0, 2 in col 1. 0 < 1? YES.
Step 7: Both orderings valid! Build the matrix: number 1 → (row 0, col 2), number 3 → (row 1, col 0), number 2 → (row 2, col 1).
Result: [[0,0,1],[3,0,0],[0,2,0]]
Brute Force — Testing Permutations on a 3×3 Grid — Watch how we try different row and column orderings, checking constraints for each combination until we find one that works.
Algorithm
- Generate all permutations of [1, 2, ..., k] for the row ordering.
- For each row permutation:
a. Verify all row constraints: for each [above, below], check that the index ofabovein the permutation is less than the index ofbelow.
b. If any constraint fails, skip this permutation. - For each valid row permutation, generate all permutations for the column ordering.
- For each column permutation:
a. Verify all column constraints: for each [left, right], check that the index ofleftis less than the index ofright.
b. If valid, build the k×k matrix by placing each number at its assigned (row, column) position. - Return the matrix, or an empty matrix if no valid combination exists.
Code
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;
class Solution {
public:
vector<vector<int>> buildMatrix(int k, vector<vector<int>>& rowConditions, vector<vector<int>>& colConditions) {
vector<int> rowPerm(k), colPerm(k);
iota(rowPerm.begin(), rowPerm.end(), 1); // [1, 2, ..., k]
iota(colPerm.begin(), colPerm.end(), 1);
auto isValid = [&](vector<int>& perm, vector<vector<int>>& conditions) {
vector<int> pos(k + 1);
for (int i = 0; i < k; i++) pos[perm[i]] = i;
for (auto& c : conditions) {
if (pos[c[0]] >= pos[c[1]]) return false;
}
return true;
};
do {
if (!isValid(rowPerm, rowConditions)) continue;
colPerm.resize(k);
iota(colPerm.begin(), colPerm.end(), 1);
do {
if (isValid(colPerm, colConditions)) {
vector<vector<int>> ans(k, vector<int>(k, 0));
vector<int> rowPos(k + 1), colPos(k + 1);
for (int i = 0; i < k; i++) {
rowPos[rowPerm[i]] = i;
colPos[colPerm[i]] = i;
}
for (int num = 1; num <= k; num++) {
ans[rowPos[num]][colPos[num]] = num;
}
return ans;
}
} while (next_permutation(colPerm.begin(), colPerm.end()));
} while (next_permutation(rowPerm.begin(), rowPerm.end()));
return {};
}
};from itertools import permutations
from typing import List
class Solution:
def buildMatrix(self, k: int, rowConditions: List[List[int]], colConditions: List[List[int]]) -> List[List[int]]:
def is_valid(perm, conditions):
pos = {val: idx for idx, val in enumerate(perm)}
return all(pos[a] < pos[b] for a, b in conditions)
nums = list(range(1, k + 1))
for row_perm in permutations(nums):
if not is_valid(row_perm, rowConditions):
continue
for col_perm in permutations(nums):
if is_valid(col_perm, colConditions):
row_pos = {val: idx for idx, val in enumerate(row_perm)}
col_pos = {val: idx for idx, val in enumerate(col_perm)}
matrix = [[0] * k for _ in range(k)]
for num in range(1, k + 1):
matrix[row_pos[num]][col_pos[num]] = num
return matrix
return []import java.util.*;
class Solution {
public int[][] buildMatrix(int k, int[][] rowConditions, int[][] colConditions) {
int[] nums = new int[k];
for (int i = 0; i < k; i++) nums[i] = i + 1;
List<int[]> rowPerms = new ArrayList<>();
generatePermutations(nums, 0, rowPerms);
for (int[] rowPerm : rowPerms) {
if (!isValid(rowPerm, rowConditions, k)) continue;
List<int[]> colPerms = new ArrayList<>();
generatePermutations(nums.clone(), 0, colPerms);
for (int[] colPerm : colPerms) {
if (isValid(colPerm, colConditions, k)) {
int[][] matrix = new int[k][k];
int[] rowPos = new int[k + 1], colPos = new int[k + 1];
for (int i = 0; i < k; i++) {
rowPos[rowPerm[i]] = i;
colPos[colPerm[i]] = i;
}
for (int num = 1; num <= k; num++) {
matrix[rowPos[num]][colPos[num]] = num;
}
return matrix;
}
}
}
return new int[][]{};
}
private boolean isValid(int[] perm, int[][] conditions, int k) {
int[] pos = new int[k + 1];
for (int i = 0; i < perm.length; i++) pos[perm[i]] = i;
for (int[] c : conditions) {
if (pos[c[0]] >= pos[c[1]]) return false;
}
return true;
}
private void generatePermutations(int[] arr, int start, List<int[]> result) {
if (start == arr.length) {
result.add(arr.clone());
return;
}
for (int i = start; i < arr.length; i++) {
int temp = arr[start]; arr[start] = arr[i]; arr[i] = temp;
generatePermutations(arr, start + 1, result);
temp = arr[start]; arr[start] = arr[i]; arr[i] = temp;
}
}
}Complexity Analysis
Time Complexity: O(k! × k! × (n + m))
We generate up to k! row permutations. For each valid row permutation, we generate up to k! column permutations. For each combination, we verify n row constraints and m column constraints. With k up to 400, k! is astronomically large — this approach is completely infeasible for any non-trivial k.
Space Complexity: O(k²)
We store the k×k result matrix and a few arrays of size k for position lookups. The permutation generation itself uses O(k) stack space.
Why This Approach Is Not Efficient
The brute force generates all possible orderings and checks each one. With k! permutations for rows and k! for columns, the total number of combinations explodes factorially. Even for k = 10, there are 10! = 3,628,800 permutations per dimension, leading to about 1.3 × 10^13 combinations — far beyond any time limit.
The fundamental problem is that we are treating the row ordering and column ordering as "guess and check" — we don't use the constraint information to construct a valid ordering directly.
Key insight: The constraints [a, b] meaning "a must come before b" form a directed graph. Finding a valid ordering that respects all "before" relationships is exactly what topological sorting does. Instead of guessing orderings and checking them, we can compute one valid ordering (if it exists) in linear time using topological sort.

Optimal Approach - Topological Sort (Kahn's BFS)
Intuition
The key insight is that we can decompose this 2D placement problem into two independent 1D ordering problems.
Think about what the constraints really say:
- Row constraints define a "who must come before whom" relationship for vertical positions.
- Column constraints define a "who must come before whom" relationship for horizontal positions.
These two dimensions are completely independent — the row a number lands in has no effect on which column it goes in, and vice versa. So we can solve them separately.
For each dimension, we have a set of "A must come before B" relationships. This is a classic topological ordering problem. If we model each constraint as a directed edge from A to B in a graph, then a topological sort of that graph gives us a valid ordering.
We use Kahn's algorithm (BFS-based topological sort). The idea is simple: repeatedly pick a node that has no dependencies (in-degree = 0), place it next in the ordering, and remove all its outgoing edges. If every node gets placed, we have a valid order. If we run out of zero-in-degree nodes before placing everyone, there must be a cycle — meaning the constraints are contradictory and no valid matrix exists.
Once we have both a valid row ordering and a valid column ordering, building the matrix is trivial: the i-th element in the row ordering goes to row i, and its column is determined by its position in the column ordering.

Step-by-Step Explanation
Let's trace through k = 3, rowConditions = [[1,2],[3,2]], colConditions = [[2,1],[3,2]].
Phase 1: Topological Sort for Row Ordering
Step 1: Build the row constraint graph. Edges: 1→2, 3→2. Compute in-degrees: node 1 has 0, node 2 has 2, node 3 has 0.
Step 2: Initialize BFS queue with all nodes that have in-degree 0. Queue = [1, 3]. These nodes have no "must be after" constraints, so they can go first.
Step 3: Dequeue node 1. Add 1 to the row order. Node 1 has edge 1→2, so decrement in-degree of node 2 from 2 to 1. Node 2 still has a dependency, so it stays out of the queue. Row order: [1].
Step 4: Dequeue node 3. Add 3 to the row order. Node 3 has edge 3→2, so decrement in-degree of node 2 from 1 to 0. Now node 2 has no remaining dependencies — add it to the queue. Row order: [1, 3].
Step 5: Dequeue node 2. Add 2 to the row order. Node 2 has no outgoing edges. Row order: [1, 3, 2].
Step 6: All 3 nodes processed. No cycle detected. Row order is valid: [1, 3, 2] means number 1 goes to row 0, number 3 goes to row 1, number 2 goes to row 2.
Phase 2: Topological Sort for Column Ordering
Step 7: Build the column constraint graph. Edges: 2→1, 3→2. In-degrees: node 1 has 1, node 2 has 1, node 3 has 0.
Step 8: Queue starts with [3] (only node with in-degree 0). Dequeue 3, add to column order. Edge 3→2: decrement in-degree of 2 from 1 to 0. Add 2 to queue. Column order: [3].
Step 9: Dequeue 2, add to column order. Edge 2→1: decrement in-degree of 1 from 1 to 0. Add 1 to queue. Column order: [3, 2].
Step 10: Dequeue 1, add to column order. Column order: [3, 2, 1]. All nodes processed — valid!
Phase 3: Build the Matrix
Step 11: Create column position mapping from column order [3, 2, 1]: number 3 → col 0, number 2 → col 1, number 1 → col 2.
Step 12: Place each number using row order [1, 3, 2] and column mapping:
- Row 0: number 1, column = colPos[1] = 2 → matrix[0][2] = 1
- Row 1: number 3, column = colPos[3] = 0 → matrix[1][0] = 3
- Row 2: number 2, column = colPos[2] = 1 → matrix[2][1] = 2
Result: [[0,0,1],[3,0,0],[0,2,0]]
Topological Sort (BFS) — Row Constraint Graph — Watch Kahn's algorithm process the row constraint graph, removing nodes with zero in-degree to build a valid top-to-bottom ordering.
Topological Sort (BFS) — Column Constraint Graph — Watch Kahn's algorithm process the column constraints, building a valid left-to-right ordering.
Matrix Construction — Combining Row and Column Orderings — Watch how we place each number at the intersection of its row position (from row topological sort) and column position (from column topological sort).
Algorithm
- Define a helper function
topologicalSort(conditions, k)that uses Kahn's algorithm:
a. Build a directed graph: for each constraint [a, b], add edge a → b.
b. Compute in-degrees for all nodes 1 to k.
c. Initialize a queue with all nodes having in-degree 0.
d. While queue is not empty:- Dequeue a node, append it to the sorted order.
- For each neighbor, decrement its in-degree. If in-degree becomes 0, enqueue it.
e. If sorted order has k elements, return it. Otherwise, a cycle exists — return empty.
- Compute
rowOrder = topologicalSort(rowConditions, k). - Compute
colOrder = topologicalSort(colConditions, k). - If either ordering is empty (cycle detected), return an empty matrix.
- Build a column position map: for each number in colOrder, record its index.
- Create a k×k matrix of zeros.
- For each index i in rowOrder, place
rowOrder[i]at row i and columncolPosMap[rowOrder[i]]. - Return the matrix.
Code
#include <vector>
#include <queue>
using namespace std;
class Solution {
public:
vector<vector<int>> buildMatrix(int k, vector<vector<int>>& rowConditions,
vector<vector<int>>& colConditions) {
vector<int> rowOrder = topologicalSort(rowConditions, k);
if (rowOrder.empty()) return {};
vector<int> colOrder = topologicalSort(colConditions, k);
if (colOrder.empty()) return {};
// Build column position mapping
vector<int> colPos(k + 1);
for (int j = 0; j < k; j++) {
colPos[colOrder[j]] = j;
}
// Construct the matrix
vector<vector<int>> ans(k, vector<int>(k, 0));
for (int i = 0; i < k; i++) {
int num = rowOrder[i];
ans[i][colPos[num]] = num;
}
return ans;
}
private:
vector<int> topologicalSort(vector<vector<int>>& conditions, int k) {
vector<vector<int>> graph(k + 1);
vector<int> inDegree(k + 1, 0);
for (auto& cond : conditions) {
graph[cond[0]].push_back(cond[1]);
inDegree[cond[1]]++;
}
queue<int> q;
for (int i = 1; i <= k; i++) {
if (inDegree[i] == 0) q.push(i);
}
vector<int> order;
while (!q.empty()) {
int node = q.front();
q.pop();
order.push_back(node);
for (int neighbor : graph[node]) {
if (--inDegree[neighbor] == 0) {
q.push(neighbor);
}
}
}
return order.size() == k ? order : vector<int>();
}
};from collections import defaultdict, deque
from typing import List
class Solution:
def buildMatrix(self, k: int, rowConditions: List[List[int]],
colConditions: List[List[int]]) -> List[List[int]]:
def topological_sort(conditions: List[List[int]]) -> List[int]:
graph = defaultdict(list)
in_degree = [0] * (k + 1)
for a, b in conditions:
graph[a].append(b)
in_degree[b] += 1
queue = deque(i for i in range(1, k + 1) if in_degree[i] == 0)
order = []
while queue:
node = queue.popleft()
order.append(node)
for neighbor in graph[node]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
return order if len(order) == k else []
row_order = topological_sort(rowConditions)
if not row_order:
return []
col_order = topological_sort(colConditions)
if not col_order:
return []
# Map each number to its column position
col_pos = {num: j for j, num in enumerate(col_order)}
# Build the matrix
matrix = [[0] * k for _ in range(k)]
for i, num in enumerate(row_order):
matrix[i][col_pos[num]] = num
return matriximport java.util.*;
class Solution {
public int[][] buildMatrix(int k, int[][] rowConditions, int[][] colConditions) {
int[] rowOrder = topologicalSort(rowConditions, k);
if (rowOrder.length == 0) return new int[][]{};
int[] colOrder = topologicalSort(colConditions, k);
if (colOrder.length == 0) return new int[][]{};
// Build column position mapping
int[] colPos = new int[k + 1];
for (int j = 0; j < k; j++) {
colPos[colOrder[j]] = j;
}
// Construct the matrix
int[][] matrix = new int[k][k];
for (int i = 0; i < k; i++) {
int num = rowOrder[i];
matrix[i][colPos[num]] = num;
}
return matrix;
}
private int[] topologicalSort(int[][] conditions, int k) {
List<List<Integer>> graph = new ArrayList<>();
int[] inDegree = new int[k + 1];
for (int i = 0; i <= k; i++) graph.add(new ArrayList<>());
for (int[] cond : conditions) {
graph.get(cond[0]).add(cond[1]);
inDegree[cond[1]]++;
}
Queue<Integer> queue = new LinkedList<>();
for (int i = 1; i <= k; i++) {
if (inDegree[i] == 0) queue.offer(i);
}
int[] order = new int[k];
int idx = 0;
while (!queue.isEmpty()) {
int node = queue.poll();
order[idx++] = node;
for (int neighbor : graph.get(node)) {
if (--inDegree[neighbor] == 0) {
queue.offer(neighbor);
}
}
}
return idx == k ? order : new int[]{};
}
}Complexity Analysis
Time Complexity: O(k² + n + m)
Where k is the matrix dimension, n is the number of row conditions, and m is the number of column conditions.
- Building each graph takes O(n) or O(m) time to iterate through conditions.
- Topological sort processes each node once and each edge once: O(k + n) for rows, O(k + m) for columns.
- Constructing the k×k matrix takes O(k²) — we initialize all k² cells to 0, then place k numbers.
- Total: O(k + n) + O(k + m) + O(k²) = O(k² + n + m).
Space Complexity: O(k² + n + m)
- The output matrix uses O(k²) space.
- The adjacency lists for both graphs use O(n + m) combined.
- In-degree arrays, orderings, and position maps each use O(k).
- The BFS queue holds at most k elements.
- Total: O(k²) for the matrix dominates.