2184. Number of Ways to Build Sturdy Brick Wall
Problem Description
You need to build a brick wall with specific dimensions: height
rows and width
units wide. You have an infinite supply of different types of bricks, where each brick type has a height of 1 unit and a width specified in the bricks
array. For example, if bricks = [1, 2, 3]
, you can use bricks of width 1, 2, or 3 units.
The key constraints are:
- Each row must be exactly
width
units long - Bricks cannot be rotated (they always have height 1)
- The wall must be "sturdy" - this means that vertical joints between bricks in adjacent rows should not align (except at the ends of the wall)
For example, if you have two adjacent rows:
- Row 1:
[brick of width 2][brick of width 3]
(total width = 5) - Row 2:
[brick of width 3][brick of width 2]
(total width = 5)
These rows would NOT be sturdy because there's a vertical joint at position 2 in row 1 and position 3 in row 2 that don't align, which is good. But if row 2 was [brick of width 2][brick of width 3]
, the joints would align at position 2, making it not sturdy.
The problem asks you to count the total number of different ways to build a sturdy wall of the given dimensions. Since this number can be very large, return the answer modulo 10^9 + 7
.
The solution approach involves:
- Finding all possible ways to fill a single row of width
width
using the available bricks - Determining which row configurations can be placed adjacent to each other (checking if joints align)
- Using dynamic programming to count the number of ways to build the entire wall of height
height
Intuition
The first insight is to recognize that this is a dynamic programming problem with a twist - we need to track compatibility between consecutive rows.
Think about building the wall row by row from bottom to top. For each row, we need to know all possible ways to arrange bricks to get exactly width
units. This is a classic backtracking problem - we try placing each brick type and recursively fill the remaining width until we reach exactly width
units.
Once we have all possible row configurations, the next challenge is determining which rows can be placed on top of each other. Two rows are compatible if their internal joints (excluding the ends at position 0 and width
) don't align. To check this, we can compare the cumulative positions where joints occur in each row. If we track the positions where bricks end in each row (except the last position), two rows are incompatible if they share any of these positions.
For example, if row A has bricks [2, 3]
for width 5, joints are at position 2. If row B has bricks [3, 2]
, joints are at position 3. These rows are compatible because positions 2 and 3 don't overlap.
Now comes the dynamic programming insight: Let dp[i][j]
represent the number of ways to build a wall of height i+1
where the top row uses configuration j
. For the first row (base case), each configuration can be used once, so dp[0][j] = 1
for all valid configurations.
For each subsequent row i
, we can place configuration j
on top only if it's compatible with the configuration used in the previous row. So dp[i][j]
equals the sum of dp[i-1][k]
for all configurations k
that are compatible with j
.
By precomputing a compatibility graph where each node represents a row configuration and edges connect compatible configurations, we can efficiently calculate the DP transitions. The final answer is the sum of all dp[height-1][j]
values, representing all possible ways to build the complete wall.
Learn more about Dynamic Programming and Bitmask patterns.
Solution Approach
The implementation consists of three main parts: generating row configurations, building a compatibility graph, and dynamic programming to count valid walls.
Step 1: Generate All Valid Row Configurations
The dfs
function uses backtracking to find all ways to fill a row of width width
:
def dfs(v):
if v > width:
return
if v == width:
s.append(t[:])
return
for x in bricks:
t.append(x)
dfs(v + x)
t.pop()
v
tracks the current width filledt
is a temporary list storing the current brick arrangement- When
v == width
, we found a valid configuration and add it tos
- We try each brick type and recursively fill the remaining width
Step 2: Build Compatibility Graph
The check
function determines if two row configurations can be adjacent:
def check(a, b):
s1, s2 = a[0], b[0]
i = j = 1
while i < len(a) and j < len(b):
if s1 == s2:
return False
if s1 < s2:
s1 += a[i]
i += 1
else:
s2 += b[j]
j += 1
return True
- It uses two pointers to track cumulative positions of joints in both rows
s1
ands2
represent the current cumulative width in each row- If
s1 == s2
at any point (except at 0 and width), the joints align and rows are incompatible - The function returns
True
if no internal joints align
A graph g
is built where g[i]
contains all row configurations compatible with configuration i
:
for i in range(n):
if check(s[i], s[i]):
g[i].append(i)
for j in range(i + 1, n):
if check(s[i], s[j]):
g[i].append(j)
g[j].append(i)
Step 3: Dynamic Programming
The DP table dp[i][j]
represents the number of ways to build a wall of height i+1
with configuration j
on top:
dp = [[0] * n for _ in range(height)]
for j in range(n):
dp[0][j] = 1 # Base case: first row
for i in range(1, height):
for j in range(n):
for k in g[j]:
dp[i][j] += dp[i - 1][k]
dp[i][j] %= mod
- Initialize first row: each configuration can be used once
- For each subsequent row, sum the ways from all compatible previous configurations
- Apply modulo operation to prevent overflow
The final answer is sum(dp[-1]) % mod
, which sums all possible ways to build a wall of the full height with any valid top configuration.
The time complexity is O(height × n²)
where n
is the number of valid row configurations, and space complexity is O(height × n)
for the DP table.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with height = 2
, width = 5
, and bricks = [1, 2, 3]
.
Step 1: Find all valid row configurations
Using backtracking, we find all ways to fill width 5:
- Starting with empty row, we try each brick:
- Place brick of width 1: remaining width = 4
- Place another 1: remaining = 3
- Place 3: total = 5 ✓ → Configuration:
[1, 1, 3]
- Place 3: total = 5 ✓ → Configuration:
- Place 2: remaining = 2
- Place 2: total = 5 ✓ → Configuration:
[1, 2, 2]
- Place 2: total = 5 ✓ → Configuration:
- Place another 1: remaining = 3
- Place brick of width 2: remaining width = 3
- Place 3: total = 5 ✓ → Configuration:
[2, 3]
- Place 3: total = 5 ✓ → Configuration:
- Place brick of width 3: remaining width = 2
- Place 2: total = 5 ✓ → Configuration:
[3, 2]
- Place 2: total = 5 ✓ → Configuration:
- Place brick of width 1: remaining width = 4
Valid configurations found:
- Config 0:
[1, 1, 3]
(joints at positions 1, 2) - Config 1:
[1, 2, 2]
(joints at positions 1, 3) - Config 2:
[2, 3]
(joint at position 2) - Config 3:
[3, 2]
(joint at position 3)
Step 2: Build compatibility graph
Check which configurations can be placed adjacent to each other:
- Config 0
[1, 1, 3]
vs Config 0[1, 1, 3]
: Joints align at positions 1 and 2 → Incompatible - Config 0
[1, 1, 3]
vs Config 1[1, 2, 2]
: Joints align at position 1 → Incompatible - Config 0
[1, 1, 3]
vs Config 2[2, 3]
: Joints align at position 2 → Incompatible - Config 0
[1, 1, 3]
vs Config 3[3, 2]
: No joints align → Compatible - Config 1
[1, 2, 2]
vs Config 1[1, 2, 2]
: Joints align at positions 1 and 3 → Incompatible - Config 1
[1, 2, 2]
vs Config 2[2, 3]
: No joints align → Compatible - Config 1
[1, 2, 2]
vs Config 3[3, 2]
: Joints align at position 3 → Incompatible - Config 2
[2, 3]
vs Config 2[2, 3]
: Joint aligns at position 2 → Incompatible - Config 2
[2, 3]
vs Config 3[3, 2]
: No joints align → Compatible - Config 3
[3, 2]
vs Config 3[3, 2]
: Joint aligns at position 3 → Incompatible
Compatibility graph:
- Config 0 → [3]
- Config 1 → [2]
- Config 2 → [1, 3]
- Config 3 → [0, 2]
Step 3: Dynamic Programming
Build DP table for height 2:
Initial state (row 0):
dp[0][0] = 1
(Config 0 on bottom)dp[0][1] = 1
(Config 1 on bottom)dp[0][2] = 1
(Config 2 on bottom)dp[0][3] = 1
(Config 3 on bottom)
Calculate row 1:
dp[1][0]
: Config 0 on top, compatible with Config 3 from row 0dp[1][0] = dp[0][3] = 1
dp[1][1]
: Config 1 on top, compatible with Config 2 from row 0dp[1][1] = dp[0][2] = 1
dp[1][2]
: Config 2 on top, compatible with Configs 1 and 3 from row 0dp[1][2] = dp[0][1] + dp[0][3] = 1 + 1 = 2
dp[1][3]
: Config 3 on top, compatible with Configs 0 and 2 from row 0dp[1][3] = dp[0][0] + dp[0][2] = 1 + 1 = 2
Final Answer: sum(dp[1]) = 1 + 1 + 2 + 2 = 6
There are 6 different ways to build a sturdy wall of height 2 and width 5 with the given bricks.
Solution Implementation
1from typing import List
2from collections import defaultdict
3
4class Solution:
5 def buildWall(self, height: int, width: int, bricks: List[int]) -> int:
6 """
7 Count the number of ways to build a wall of given height and width.
8 Adjacent rows cannot have aligned vertical seams (except at edges).
9
10 Args:
11 height: The height of the wall (number of rows)
12 width: The width of the wall
13 bricks: List of available brick sizes
14
15 Returns:
16 Number of valid wall configurations modulo 10^9 + 7
17 """
18
19 def generate_row_patterns(current_width):
20 """
21 Generate all possible ways to fill a single row of the wall.
22 Uses DFS to explore all combinations of bricks that sum to width.
23 """
24 if current_width > width:
25 return
26
27 if current_width == width:
28 # Found a valid row pattern
29 all_row_patterns.append(current_pattern[:])
30 return
31
32 # Try adding each brick type
33 for brick_size in bricks:
34 current_pattern.append(brick_size)
35 generate_row_patterns(current_width + brick_size)
36 current_pattern.pop()
37
38 def are_rows_compatible(row_a, row_b):
39 """
40 Check if two row patterns can be placed adjacent to each other.
41 Two rows are compatible if their internal seams don't align.
42 """
43 # Calculate cumulative positions of seams (excluding the final edge)
44 sum_a, sum_b = row_a[0], row_b[0]
45 idx_a, idx_b = 1, 1
46
47 # Compare seam positions
48 while idx_a < len(row_a) and idx_b < len(row_b):
49 if sum_a == sum_b:
50 # Found aligned seams - rows are incompatible
51 return False
52
53 # Advance the pointer with smaller cumulative sum
54 if sum_a < sum_b:
55 sum_a += row_a[idx_a]
56 idx_a += 1
57 else:
58 sum_b += row_b[idx_b]
59 idx_b += 1
60
61 return True
62
63 # Constants
64 MOD = 10**9 + 7
65
66 # Generate all possible row patterns
67 all_row_patterns = []
68 current_pattern = []
69 generate_row_patterns(0)
70
71 # Build adjacency graph for compatible row patterns
72 compatibility_graph = defaultdict(list)
73 num_patterns = len(all_row_patterns)
74
75 for i in range(num_patterns):
76 # Check if pattern is compatible with itself
77 if are_rows_compatible(all_row_patterns[i], all_row_patterns[i]):
78 compatibility_graph[i].append(i)
79
80 # Check compatibility with other patterns
81 for j in range(i + 1, num_patterns):
82 if are_rows_compatible(all_row_patterns[i], all_row_patterns[j]):
83 compatibility_graph[i].append(j)
84 compatibility_graph[j].append(i)
85
86 # Dynamic programming to count valid wall configurations
87 # dp[row][pattern] = number of ways to build wall up to row using pattern at this row
88 dp = [[0] * num_patterns for _ in range(height)]
89
90 # Initialize first row - each pattern can be used once
91 for pattern_idx in range(num_patterns):
92 dp[0][pattern_idx] = 1
93
94 # Fill remaining rows
95 for row in range(1, height):
96 for current_pattern in range(num_patterns):
97 # Sum ways from all compatible patterns in previous row
98 for compatible_pattern in compatibility_graph[current_pattern]:
99 dp[row][current_pattern] += dp[row - 1][compatible_pattern]
100 dp[row][current_pattern] %= MOD
101
102 # Sum all ways to build the complete wall
103 total_ways = sum(dp[-1]) % MOD
104 return total_ways
105
1class Solution {
2 // Store all valid brick patterns for a single row
3 private List<List<Integer>> validPatterns = new ArrayList<>();
4 // Temporary list to build current pattern during DFS
5 private List<Integer> currentPattern = new ArrayList<>();
6 // Modulo value for large number handling
7 private static final int MOD = (int) 1e9 + 7;
8 // Target width of the wall
9 private int targetWidth;
10 // Available brick sizes
11 private int[] availableBricks;
12
13 /**
14 * Calculate the number of ways to build a wall of given height and width
15 * @param height The height of the wall (number of rows)
16 * @param width The width of the wall
17 * @param bricks Array of available brick sizes
18 * @return Number of ways to build the wall modulo 10^9 + 7
19 */
20 public int buildWall(int height, int width, int[] bricks) {
21 this.targetWidth = width;
22 this.availableBricks = bricks;
23
24 // Generate all valid patterns for a single row
25 generateAllPatterns(0);
26
27 int patternCount = validPatterns.size();
28
29 // Build adjacency list where g[i] contains indices of patterns
30 // that can be placed on top of pattern i
31 List<Integer>[] adjacencyList = new List[patternCount];
32 Arrays.setAll(adjacencyList, k -> new ArrayList<>());
33
34 // Check compatibility between all pairs of patterns
35 for (int i = 0; i < patternCount; ++i) {
36 // Check if pattern can be placed on top of itself
37 if (isCompatible(validPatterns.get(i), validPatterns.get(i))) {
38 adjacencyList[i].add(i);
39 }
40 // Check compatibility with other patterns
41 for (int j = i + 1; j < patternCount; ++j) {
42 if (isCompatible(validPatterns.get(i), validPatterns.get(j))) {
43 adjacencyList[i].add(j);
44 adjacencyList[j].add(i);
45 }
46 }
47 }
48
49 // Dynamic programming table
50 // dp[i][j] = number of ways to build wall up to row i ending with pattern j
51 int[][] dp = new int[height][patternCount];
52
53 // Initialize first row - each pattern can be used once
54 for (int j = 0; j < patternCount; ++j) {
55 dp[0][j] = 1;
56 }
57
58 // Fill DP table for remaining rows
59 for (int row = 1; row < height; ++row) {
60 for (int currentPatternIdx = 0; currentPatternIdx < patternCount; ++currentPatternIdx) {
61 // Sum ways from all compatible patterns in previous row
62 for (int prevPatternIdx : adjacencyList[currentPatternIdx]) {
63 dp[row][currentPatternIdx] = (dp[row][currentPatternIdx] + dp[row - 1][prevPatternIdx]) % MOD;
64 }
65 }
66 }
67
68 // Sum all ways to build the complete wall
69 int totalWays = 0;
70 for (int j = 0; j < patternCount; ++j) {
71 totalWays = (totalWays + dp[height - 1][j]) % MOD;
72 }
73
74 return totalWays;
75 }
76
77 /**
78 * Check if two brick patterns are compatible (no vertical edges align)
79 * @param patternA First brick pattern
80 * @param patternB Second brick pattern
81 * @return true if patterns are compatible, false otherwise
82 */
83 private boolean isCompatible(List<Integer> patternA, List<Integer> patternB) {
84 // Track cumulative width for each pattern
85 int cumulativeWidthA = patternA.get(0);
86 int cumulativeWidthB = patternB.get(0);
87 int indexA = 1;
88 int indexB = 1;
89
90 // Compare edge positions until one pattern is exhausted
91 while (indexA < patternA.size() && indexB < patternB.size()) {
92 // If edges align, patterns are incompatible
93 if (cumulativeWidthA == cumulativeWidthB) {
94 return false;
95 }
96 // Advance the pattern with smaller cumulative width
97 if (cumulativeWidthA < cumulativeWidthB) {
98 cumulativeWidthA += patternA.get(indexA++);
99 } else {
100 cumulativeWidthB += patternB.get(indexB++);
101 }
102 }
103 return true;
104 }
105
106 /**
107 * Generate all valid brick patterns using DFS
108 * @param currentWidth Current cumulative width of bricks in pattern
109 */
110 private void generateAllPatterns(int currentWidth) {
111 // Prune if exceeded target width
112 if (currentWidth > targetWidth) {
113 return;
114 }
115 // Found valid pattern
116 if (currentWidth == targetWidth) {
117 validPatterns.add(new ArrayList<>(currentPattern));
118 return;
119 }
120 // Try adding each available brick size
121 for (int brickSize : availableBricks) {
122 currentPattern.add(brickSize);
123 generateAllPatterns(currentWidth + brickSize);
124 currentPattern.remove(currentPattern.size() - 1);
125 }
126 }
127}
128
1class Solution {
2public:
3 vector<int> bricks;
4 int targetWidth;
5 const int MOD = 1e9 + 7;
6 vector<vector<int>> validRows; // All valid brick arrangements for a single row
7 vector<int> currentRow; // Temporary vector for building row combinations
8
9 int buildWall(int height, int width, vector<int>& bricks) {
10 this->targetWidth = width;
11 this->bricks = bricks;
12
13 // Step 1: Generate all valid brick arrangements for a single row
14 generateValidRows(0);
15
16 // Clear temporary vector after use
17 currentRow.clear();
18
19 int numRows = validRows.size();
20
21 // Step 2: Build adjacency graph - which rows can be placed on top of each other
22 vector<vector<int>> adjacencyGraph(numRows);
23 for (int i = 0; i < numRows; ++i) {
24 // Check if row i can be placed on top of itself
25 if (canStack(validRows[i], validRows[i])) {
26 adjacencyGraph[i].push_back(i);
27 }
28 // Check if row i can be placed on top of row j (and vice versa)
29 for (int j = i + 1; j < numRows; ++j) {
30 if (canStack(validRows[i], validRows[j])) {
31 adjacencyGraph[i].push_back(j);
32 adjacencyGraph[j].push_back(i);
33 }
34 }
35 }
36
37 // Step 3: Dynamic programming to count valid wall configurations
38 // dp[i][j] = number of ways to build wall of height i+1 with row j on top
39 vector<vector<int>> dp(height, vector<int>(numRows));
40
41 // Base case: first row can be any valid row arrangement
42 for (int j = 0; j < numRows; ++j) {
43 dp[0][j] = 1;
44 }
45
46 // Fill dp table for remaining heights
47 for (int i = 1; i < height; ++i) {
48 for (int j = 0; j < numRows; ++j) {
49 // Sum up ways from all compatible previous rows
50 for (int prevRow : adjacencyGraph[j]) {
51 dp[i][j] = (dp[i][j] + dp[i - 1][prevRow]) % MOD;
52 }
53 }
54 }
55
56 // Step 4: Calculate total number of valid walls
57 int totalWays = 0;
58 for (int j = 0; j < numRows; ++j) {
59 totalWays = (totalWays + dp[height - 1][j]) % MOD;
60 }
61
62 return totalWays;
63 }
64
65private:
66 // Check if two rows can be stacked (no vertical joints align)
67 bool canStack(vector<int>& rowA, vector<int>& rowB) {
68 int sumA = rowA[0]; // Running sum of bricks in row A
69 int sumB = rowB[0]; // Running sum of bricks in row B
70 int indexA = 1;
71 int indexB = 1;
72
73 // Compare cumulative widths to find if any joints align
74 while (indexA < rowA.size() && indexB < rowB.size()) {
75 if (sumA == sumB) {
76 // Found aligned joint (except at the end)
77 return false;
78 }
79 // Advance the row with smaller cumulative width
80 if (sumA < sumB) {
81 sumA += rowA[indexA++];
82 } else {
83 sumB += rowB[indexB++];
84 }
85 }
86 return true; // No aligned joints found
87 }
88
89 // Generate all valid brick arrangements for a single row using DFS
90 void generateValidRows(int currentWidth) {
91 // Prune if we exceed target width
92 if (currentWidth > targetWidth) {
93 return;
94 }
95
96 // Found a valid row arrangement
97 if (currentWidth == targetWidth) {
98 validRows.push_back(currentRow);
99 return;
100 }
101
102 // Try adding each brick type
103 for (int brickSize : bricks) {
104 currentRow.push_back(brickSize);
105 generateValidRows(currentWidth + brickSize);
106 currentRow.pop_back(); // Backtrack
107 }
108 }
109};
110
1let bricks: number[];
2let targetWidth: number;
3const MOD = 1e9 + 7;
4let validRows: number[][]; // All valid brick arrangements for a single row
5let currentRow: number[]; // Temporary array for building row combinations
6
7function buildWall(height: number, width: number, bricksInput: number[]): number {
8 targetWidth = width;
9 bricks = bricksInput;
10 validRows = [];
11 currentRow = [];
12
13 // Step 1: Generate all valid brick arrangements for a single row
14 generateValidRows(0);
15
16 // Clear temporary array after use
17 currentRow = [];
18
19 const numRows = validRows.length;
20
21 // Step 2: Build adjacency graph - which rows can be placed on top of each other
22 const adjacencyGraph: number[][] = Array(numRows).fill(null).map(() => []);
23
24 for (let i = 0; i < numRows; i++) {
25 // Check if row i can be placed on top of itself
26 if (canStack(validRows[i], validRows[i])) {
27 adjacencyGraph[i].push(i);
28 }
29 // Check if row i can be placed on top of row j (and vice versa)
30 for (let j = i + 1; j < numRows; j++) {
31 if (canStack(validRows[i], validRows[j])) {
32 adjacencyGraph[i].push(j);
33 adjacencyGraph[j].push(i);
34 }
35 }
36 }
37
38 // Step 3: Dynamic programming to count valid wall configurations
39 // dp[i][j] = number of ways to build wall of height i+1 with row j on top
40 const dp: number[][] = Array(height).fill(null).map(() => Array(numRows).fill(0));
41
42 // Base case: first row can be any valid row arrangement
43 for (let j = 0; j < numRows; j++) {
44 dp[0][j] = 1;
45 }
46
47 // Fill dp table for remaining heights
48 for (let i = 1; i < height; i++) {
49 for (let j = 0; j < numRows; j++) {
50 // Sum up ways from all compatible previous rows
51 for (const prevRow of adjacencyGraph[j]) {
52 dp[i][j] = (dp[i][j] + dp[i - 1][prevRow]) % MOD;
53 }
54 }
55 }
56
57 // Step 4: Calculate total number of valid walls
58 let totalWays = 0;
59 for (let j = 0; j < numRows; j++) {
60 totalWays = (totalWays + dp[height - 1][j]) % MOD;
61 }
62
63 return totalWays;
64}
65
66// Check if two rows can be stacked (no vertical joints align)
67function canStack(rowA: number[], rowB: number[]): boolean {
68 let sumA = rowA[0]; // Running sum of bricks in row A
69 let sumB = rowB[0]; // Running sum of bricks in row B
70 let indexA = 1;
71 let indexB = 1;
72
73 // Compare cumulative widths to find if any joints align
74 while (indexA < rowA.length && indexB < rowB.length) {
75 if (sumA === sumB) {
76 // Found aligned joint (except at the end)
77 return false;
78 }
79 // Advance the row with smaller cumulative width
80 if (sumA < sumB) {
81 sumA += rowA[indexA++];
82 } else {
83 sumB += rowB[indexB++];
84 }
85 }
86 return true; // No aligned joints found
87}
88
89// Generate all valid brick arrangements for a single row using DFS
90function generateValidRows(currentWidth: number): void {
91 // Prune if we exceed target width
92 if (currentWidth > targetWidth) {
93 return;
94 }
95
96 // Found a valid row arrangement
97 if (currentWidth === targetWidth) {
98 validRows.push([...currentRow]);
99 return;
100 }
101
102 // Try adding each brick type
103 for (const brickSize of bricks) {
104 currentRow.push(brickSize);
105 generateValidRows(currentWidth + brickSize);
106 currentRow.pop(); // Backtrack
107 }
108}
109
Time and Space Complexity
Time Complexity: O(2^width * n^2 + height * n^2)
where n
is the number of valid row configurations.
- The
dfs
function explores all possible ways to fill a row with bricks. In the worst case, using bricks of size 1, this creates2^width
recursive calls (at each position, we can either place a brick or not). - After generating all valid row configurations, there are
n
such configurations wheren ≤ 2^width
. - The
check
function runs inO(width)
time to verify if two rows are compatible (no aligned cracks). - Building the adjacency graph requires
O(n^2)
comparisons, each takingO(width)
time, resulting inO(n^2 * width)
. - The dynamic programming phase iterates through
height
rows, and for each of then
configurations, it checks all adjacent configurations, resulting inO(height * n^2)
operations. - Overall:
O(2^width + n^2 * width + height * n^2)
≈O(2^width * n^2 + height * n^2)
Space Complexity: O(n * width + n^2 + height * n)
- The
s
list stores all valid row configurations, with each configuration having at mostwidth
elements:O(n * width)
. - The adjacency graph
g
stores edges between compatible rows:O(n^2)
in the worst case. - The DP table
dp
has dimensionsheight × n
:O(height * n)
. - The recursion stack depth for
dfs
is at mostwidth
:O(width)
. - Overall:
O(n * width + n^2 + height * n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrectly Handling Self-Compatibility Check
The Pitfall: A critical oversight is forgetting to check if a row pattern can be placed adjacent to itself. Many developers assume they only need to check compatibility between different patterns, leading to incorrect results when the same pattern appears in consecutive rows.
Why It Happens: The nested loop that builds the compatibility graph typically compares pattern i
with patterns j
where j > i
. This misses the case where pattern i
needs to be checked against itself.
Incorrect Implementation:
# WRONG: Missing self-compatibility check
for i in range(num_patterns):
for j in range(i + 1, num_patterns): # Only checks different patterns
if are_rows_compatible(all_row_patterns[i], all_row_patterns[j]):
compatibility_graph[i].append(j)
compatibility_graph[j].append(i)
Correct Implementation:
# CORRECT: Includes self-compatibility check
for i in range(num_patterns):
# Check if pattern can be adjacent to itself
if are_rows_compatible(all_row_patterns[i], all_row_patterns[i]):
compatibility_graph[i].append(i)
# Check compatibility with other patterns
for j in range(i + 1, num_patterns):
if are_rows_compatible(all_row_patterns[i], all_row_patterns[j]):
compatibility_graph[i].append(j)
compatibility_graph[j].append(i)
Example Where This Matters:
Consider width = 4
and bricks = [1, 2]
:
- Pattern
[1, 1, 2]
has joints at positions 1, 2, and 4 - When placed adjacent to itself, joints at positions 1 and 2 align perfectly
- This pattern is NOT self-compatible and shouldn't be allowed in consecutive rows
2. Overflow Due to Missing Modulo Operations
The Pitfall: Forgetting to apply the modulo operation during intermediate calculations can cause integer overflow, especially when the wall height is large.
Incorrect Implementation:
# WRONG: Missing modulo in the inner loop
for row in range(1, height):
for current_pattern in range(num_patterns):
for compatible_pattern in compatibility_graph[current_pattern]:
dp[row][current_pattern] += dp[row - 1][compatible_pattern]
# Modulo applied too late - overflow may have already occurred
dp[row][current_pattern] %= MOD
Correct Implementation:
# CORRECT: Apply modulo after each addition
for row in range(1, height):
for current_pattern in range(num_patterns):
for compatible_pattern in compatibility_graph[current_pattern]:
dp[row][current_pattern] += dp[row - 1][compatible_pattern]
dp[row][current_pattern] %= MOD # Apply immediately to prevent overflow
3. Misunderstanding Joint Alignment Logic
The Pitfall: The compatibility check function can be tricky to implement correctly. A common mistake is comparing the wrong values or incorrectly handling the pointers.
Incorrect Implementation:
# WRONG: Comparing indices instead of cumulative positions
def are_rows_compatible(row_a, row_b):
idx_a, idx_b = 0, 0
while idx_a < len(row_a) - 1 and idx_b < len(row_b) - 1:
if idx_a == idx_b: # Wrong: comparing indices not positions
return False
if row_a[idx_a] < row_b[idx_b]:
idx_a += 1
else:
idx_b += 1
return True
Correct Implementation:
# CORRECT: Comparing cumulative positions of joints
def are_rows_compatible(row_a, row_b):
sum_a, sum_b = row_a[0], row_b[0]
idx_a, idx_b = 1, 1
while idx_a < len(row_a) and idx_b < len(row_b):
if sum_a == sum_b: # Correct: comparing cumulative positions
return False
if sum_a < sum_b:
sum_a += row_a[idx_a]
idx_a += 1
else:
sum_b += row_b[idx_b]
idx_b += 1
return True
Why This Matters: Joint positions are determined by the cumulative sum of brick widths, not by the index in the array. For example, [2, 3]
has a joint at position 2, while [1, 1, 3]
has joints at positions 1 and 2.
What are the most two important steps in writing a depth first search function? (Select 2)
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Bitmask and Dynamic Programming Bit manipulation is a crucial aspect of computer programming and one of the most powerful tools for bit manipulation is bitmasks Let's first understand what a bit is A bit is a binary digit It's the smallest piece of data in a computer and can be
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!