Advance Conway's board one generation where every cell updates simultaneously. The O(1)-space trick: pack the next state into bit 1 of each cell while the currentstate stays in bit 0, then shift everything right at the end.
Conway's Game of Life. Given an m × n board of 1 (live) / 0 (dead) cells, produce the next generation, applying these rules to all cells at once: a live cell with < 2 or > 3 live neighbors dies; a live cell with 2 or 3 survives; a dead cell with exactly 3 live neighbors is born.
Input: [[0,1,0],[0,0,1],[1,1,1],[0,0,0]]
Output: [[0,0,0],[1,0,1],[0,1,1],[0,1,0]]
bit 0 and write the next state into bit 1. So a cell becomes one of 0 (00, dead→dead), 1 (01, live→dead), 2 (10, dead→live), or 3 (11, live→live). Neighbor counts read value & 1 (the untouched current state). A final pass does value >>= 1 to reveal the next state.board[nr][nc] & 1. Using & 1 reads the original state even after earlier cells have had their high bit set.board[r][c] & 1) and has 2 or 3 live neighbors, it survives → board[r][c] |= 2. If it's dead with exactly 3, it's born → board[r][c] |= 2. Otherwise leave bit 1 as 0 (next state dead). The low bit is never touched.board[r][c] >>= 1, discarding the old state and leaving only the next state in bit 0.& 1: once you start writing bit 1, a neighbor read as 3 instead of 1 would over-count.Both run in O(m·n) time — every cell looks at a constant 8 neighbors. The naive approach copies the board into a second buffer (so reads always see the original); the bit trick removes that buffer for O(1) extra space.
& 1.1▶function gameOfLife(board: number[][]): void {2▶ const rows = board.length;3▶ const cols = board[0].length;45 // Bit 0 = current state, bit 1 = next state.6 for (let r = 0; r < rows; r++) {7 for (let c = 0; c < cols; c++) {8 let live = 0;9 for (let dr = -1; dr <= 1; dr++) {10 for (let dc = -1; dc <= 1; dc++) {11 if (dr === 0 && dc === 0) continue;12 const nr = r + dr, nc = c + dc;13 if (nr < 0 || nr >= rows || nc < 0 || nc >= cols) continue;14 live += board[nr][nc] & 1; // read CURRENT via low bit15 }16 }17 // Rules: live stays/dies, dead may be born. Write next into bit 1.18 if ((board[r][c] & 1) === 1) {19 if (live === 2 || live === 3) board[r][c] |= 2;20 } else if (live === 3) {21 board[r][c] |= 2;22 }23 }24 }2526 // Second pass: drop the old state, shift next state down.27 for (let r = 0; r < rows; r++) {28 for (let c = 0; c < cols; c++) {29 board[r][c] >>= 1;30 }31 }32}
function gameOfLife(board: number[][]): void {
const rows = board.length;
const cols = board[0].length;
// Bit 0 = current state, bit 1 = next state.
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
let live = 0;
for (let dr = -1; dr <= 1; dr++) {
for (let dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue;
const nr = r + dr, nc = c + dc;
if (nr < 0 || nr >= rows || nc < 0 || nc >= cols) continue;
live += board[nr][nc] & 1; // read CURRENT via low bit
}
}
// Rules: live stays/dies, dead may be born. Write next into bit 1.
if ((board[r][c] & 1) === 1) {
if (live === 2 || live === 3) board[r][c] |= 2;
} else if (live === 3) {
board[r][c] |= 2;
}
}
}
// Second pass: drop the old state, shift next state down.
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
board[r][c] >>= 1;
}
}
}(0,0) and out-of-bounds. Add board[nr][nc] & 1 — the & 1 masks off any next-state bit already written, so we always count the original liveness.board[r][c] & 1) and has 2 or 3 neighbors, it survives, so set bit 1 with |= 2. A dead cell with exactly 3 neighbors is born, likewise |= 2. Every other case leaves bit 1 at 0, which already encodes "next = dead".board[r][c] >>= 1, throwing away the old low bit and dropping the next state into bit 0. The board now holds exactly the next generation.& 1 when counting neighbors?" Because by the time we reach a later cell, earlier cells may already have bit 1 set. Masking with & 1 recovers the original state so the count is correct.(r,c) coordinates and tally neighbor counts in a map; you never materialize empty space.-1 and a newborn dead cell as 2, count with Math.abs(v) === 1, then normalize. Same idea, different encoding.nr/nc against 0..rows/colshandles them — border cells simply have fewer neighbors.function gameOfLife(board: number[][]): void {
const rows = board.length;
const cols = board[0].length;
// Bit 0 = current state, bit 1 = next state.
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
let live = 0;
for (let dr = -1; dr <= 1; dr++) {
for (let dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue;
const nr = r + dr, nc = c + dc;
if (nr < 0 || nr >= rows || nc < 0 || nc >= cols) continue;
live += board[nr][nc] & 1; // read CURRENT via low bit
}
}
// Rules: live stays/dies, dead may be born. Write next into bit 1.
if ((board[r][c] & 1) === 1) {
if (live === 2 || live === 3) board[r][c] |= 2;
} else if (live === 3) {
board[r][c] |= 2;
}
}
}
// Second pass: drop the old state, shift next state down.
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
board[r][c] >>= 1;
}
}
}Snapshot the original grid, then compute each cell's next value by reading neighbors from the untouched copy. The simplest correct way to enforce simultaneous updates.
function gameOfLife(board: number[][]): void {
const rows = board.length;
const cols = board[0].length;
const prev = board.map((row) => row.slice());
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
let live = 0;
for (let dr = -1; dr <= 1; dr++) {
for (let dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue;
const nr = r + dr, nc = c + dc;
if (nr < 0 || nr >= rows || nc < 0 || nc >= cols) continue;
live += prev[nr][nc];
}
}
if (prev[r][c] === 1) {
board[r][c] = live === 2 || live === 3 ? 1 : 0;
} else {
board[r][c] = live === 3 ? 1 : 0;
}
}
}
}O(m·n) extra space. The 2-bit encoding stores the next state inside spare bits of the same cell to drop that to O(1).| "update all cells simultaneously" | encode old + new state in one slot |
| in-place grid update, O(1) space | 2-bit encoding (bit0 now, bit1 next) |
| neighbor read must see original value | mask with value & 1 |
| reveal new state after a full pass | shift every cell value >> 1 |
// Encode next state in bit 1, keep current in bit 0.
for (let r = 0; r < rows; r++)
for (let c = 0; c < cols; c++) {
let live = 0;
// count 8 neighbors using (board[nr][nc] & 1)
if ((board[r][c] & 1) === 1) {
if (live === 2 || live === 3) board[r][c] |= 2; // survives
} else if (live === 3) board[r][c] |= 2; // born
}
// Shift everyone down to reveal the next state.
for (let r = 0; r < rows; r++)
for (let c = 0; c < cols; c++) board[r][c] >>= 1;board[nr][nc] & 1 instead of board[nr][nc]?3 after pass 1. What does that mean?[[0,1,0],[0,0,1],[1,1,1],[0,0,0]], what is the next generation?