Facebook Pixel

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:

  1. Each row must be exactly width units long
  2. Bricks cannot be rotated (they always have height 1)
  3. 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:

  1. Finding all possible ways to fill a single row of width width using the available bricks
  2. Determining which row configurations can be placed adjacent to each other (checking if joints align)
  3. Using dynamic programming to count the number of ways to build the entire wall of height height
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 filled
  • t is a temporary list storing the current brick arrangement
  • When v == width, we found a valid configuration and add it to s
  • 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 and s2 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 Evaluator

Example 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 2: remaining = 2
        • Place 2: total = 5 ✓ → Configuration: [1, 2, 2]
    • Place brick of width 2: remaining width = 3
      • Place 3: total = 5 ✓ → Configuration: [2, 3]
    • Place brick of width 3: remaining width = 2
      • Place 2: total = 5 ✓ → Configuration: [3, 2]

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 0
    • dp[1][0] = dp[0][3] = 1
  • dp[1][1]: Config 1 on top, compatible with Config 2 from row 0
    • dp[1][1] = dp[0][2] = 1
  • dp[1][2]: Config 2 on top, compatible with Configs 1 and 3 from row 0
    • dp[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 0
    • dp[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 creates 2^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 where n ≤ 2^width.
  • The check function runs in O(width) time to verify if two rows are compatible (no aligned cracks).
  • Building the adjacency graph requires O(n^2) comparisons, each taking O(width) time, resulting in O(n^2 * width).
  • The dynamic programming phase iterates through height rows, and for each of the n configurations, it checks all adjacent configurations, resulting in O(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 most width 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 dimensions height × n: O(height * n).
  • The recursion stack depth for dfs is at most width: 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What are the most two important steps in writing a depth first search function? (Select 2)


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More