File tree

1 file changed

+135
-0
lines changed

1 file changed

+135
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
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

Comments
 (0)