|
| 1 | +// The cache[rows] = adjacency list of compatible column-patterns |
| 2 | +const compatibilityListCache = {}; |
| 3 | + |
| 4 | +/** |
| 5 | +* Build (and cache) all valid column-patterns of height `rows` |
| 6 | +* plus, for each pattern index, the list of all compatible pattern indices. |
| 7 | +* @param rows {number} - height of the column-patterns |
| 8 | +* @returns {number[][]} - adjacency list of compatible column-patterns |
| 9 | +*/ |
| 10 | +function ensureCompatibilityList(rows) { |
| 11 | +// Return cached adjacency list if already computed for this `rows` |
| 12 | +if (compatibilityListCache[rows]) { |
| 13 | +return compatibilityListCache[rows]; |
| 14 | +} |
| 15 | + |
| 16 | +// 1. Generate all valid patterns (column colorings) with no adjacent cells the same color |
| 17 | +const validColumnPatterns = []; |
| 18 | +const currentPattern = new Array(rows); |
| 19 | + |
| 20 | +function generatePatterns(position) { |
| 21 | +// Save a valid pattern when filled |
| 22 | +if (position === rows) { |
| 23 | +validColumnPatterns.push(currentPattern.slice()); |
| 24 | +return; |
| 25 | +} |
| 26 | + |
| 27 | +for (let colorIndex = 0; colorIndex < 3; colorIndex++) { |
| 28 | +// Skip if same color as previous row (adjacent) |
| 29 | +if (position > 0 && currentPattern[position - 1] === colorIndex) { |
| 30 | +continue; |
| 31 | +} |
| 32 | + |
| 33 | +currentPattern[position] = colorIndex; |
| 34 | +generatePatterns(position + 1); |
| 35 | +} |
| 36 | +} |
| 37 | + |
| 38 | +generatePatterns(0); |
| 39 | + |
| 40 | +// 2. For each pattern, find all compatible patterns (next column) |
| 41 | +// Patterns are compatible if no row in the same position has the same color |
| 42 | +const patternCount = validColumnPatterns.length; |
| 43 | +const compatibilityAdjacencyList = Array.from( |
| 44 | +{ length: patternCount }, |
| 45 | +() => [], |
| 46 | +); |
| 47 | + |
| 48 | +for (let firstPatternIndex = 0; firstPatternIndex < patternCount; firstPatternIndex++) { |
| 49 | +const firstPattern = validColumnPatterns[firstPatternIndex]; |
| 50 | + |
| 51 | +for (let secondPatternIndex = 0; secondPatternIndex < patternCount; secondPatternIndex++) { |
| 52 | +const secondPattern = validColumnPatterns[secondPatternIndex]; |
| 53 | +let isCompatible = true; |
| 54 | + |
| 55 | +for (let rowIndex = 0; rowIndex < rows; rowIndex++) { |
| 56 | +// Not compatible if any row has the same color in adjacent columns |
| 57 | +if (firstPattern[rowIndex] === secondPattern[rowIndex]) { |
| 58 | +isCompatible = false; |
| 59 | +break; |
| 60 | +} |
| 61 | +} |
| 62 | + |
| 63 | +// If compatible, add to the adjacency list |
| 64 | +if (isCompatible) { |
| 65 | +compatibilityAdjacencyList[firstPatternIndex].push(secondPatternIndex); |
| 66 | +} |
| 67 | +} |
| 68 | +} |
| 69 | + |
| 70 | +// Cache and return result |
| 71 | +compatibilityListCache[rows] = compatibilityAdjacencyList; |
| 72 | +return compatibilityAdjacencyList; |
| 73 | +} |
| 74 | + |
| 75 | +/** |
| 76 | +* Count the number of valid ways to color an m x n grid |
| 77 | +* - No two adjacent cells in a row or column have the same color (3 colors) |
| 78 | +* @param rowCount {number} - number of rows in the grid |
| 79 | +* @param columnCount {number} - number of columns in the grid |
| 80 | +* @return {number} - number of valid colorings (modulo 1_000_000_007) |
| 81 | +*/ |
| 82 | +function colorTheGrid(rowCount, columnCount) { |
| 83 | +const MODULO = 1_000_000_007; |
| 84 | + |
| 85 | +// 1. Precompute compatibility for all patterns of one column (height = rowCount) |
| 86 | +const compatibilityAdjacencyList = ensureCompatibilityList(rowCount); |
| 87 | +const patternCount = compatibilityAdjacencyList.length; |
| 88 | + |
| 89 | +// 2. DP buffer: waysForPreviousColumn[i] = #ways to paint up to previous column with the ending pattern i |
| 90 | +let waysForPreviousColumn = new Int32Array(patternCount).fill(1); // Base case: first column, all patterns valid |
| 91 | +let waysForCurrentColumn = new Int32Array(patternCount); // Temp buffer for new column |
| 92 | + |
| 93 | +// 3. Process each column left-to-right (skip first column, which is the base-case) |
| 94 | +for (let columnIndex = 1; columnIndex < columnCount; columnIndex++) { |
| 95 | +waysForCurrentColumn.fill(0); |
| 96 | + |
| 97 | +for (let previousPatternIndex = 0; previousPatternIndex < patternCount; previousPatternIndex++) { |
| 98 | +const waysCount = waysForPreviousColumn[previousPatternIndex]; |
| 99 | +// Skip if no ways |
| 100 | +if (waysCount === 0) { |
| 101 | +continue; |
| 102 | +} |
| 103 | + |
| 104 | +// For each compatible next pattern, add count to the next column state |
| 105 | +const compatibleNextPatterns = compatibilityAdjacencyList[previousPatternIndex]; |
| 106 | +for (let neigrIndex = 0; neigrIndex < compatibleNextPatterns.length; neigrIndex++) { |
| 107 | +const nextPatternIndex = compatibleNextPatterns[neigrIndex]; |
| 108 | +let updatedWays = waysForCurrentColumn[nextPatternIndex] + waysCount; |
| 109 | + |
| 110 | +// Keep result within modulo constraint |
| 111 | +if (updatedWays >= MODULO) { |
| 112 | +updatedWays -= MODULO; |
| 113 | +} |
| 114 | + |
| 115 | +waysForCurrentColumn[nextPatternIndex] = updatedWays; |
| 116 | +} |
| 117 | +} |
| 118 | + |
| 119 | +// Swap buffers for next column (no reallocation, just swap roles) |
| 120 | +const swapTemporary = waysForPreviousColumn; |
| 121 | +waysForPreviousColumn = waysForCurrentColumn; |
| 122 | +waysForCurrentColumn = swapTemporary; |
| 123 | +} |
| 124 | + |
| 125 | +// 4. Final answer: Sum ways for all patterns in the last column |
| 126 | +let totalWays = 0; |
| 127 | +for (let patternIndex = 0; patternIndex < patternCount; patternIndex++) { |
| 128 | +totalWays += waysForPreviousColumn[patternIndex]; |
| 129 | +if (totalWays >= MODULO) { |
| 130 | +totalWays -= MODULO; |
| 131 | +} |
| 132 | +} |
| 133 | + |
| 134 | +return totalWays; |
| 135 | +} |
0 commit comments