We have a 2D grid where each cell is either alive (1) or dead (0). We need to apply four rules to every cell at the same time to produce the next generation.
The complication is the word "simultaneously." If we update cell (0,0) first and then move on to cell (0,1), cell (0,1)'s neighbors have already changed, so the result would differ from applying all updates at once. We need a way to read the original state of every cell while writing the new states.
The four rules reduce to two conditions for a cell being alive in the next generation:
In all other cases, the cell is dead in the next generation.
Make a copy of the board. Read neighbor counts from the copy, which never changes, and write the new states directly to the original board. Since every neighbor count comes from the unmodified copy, the order in which we update cells no longer matters.
The copy is easy to reason about, but it uses O(m * n) extra space to store the whole board. The follow-up asks for an in-place solution. Each cell is stored as an integer but carries only one bit of information (0 or 1), so the unused bits can hold a second value. The next approach uses them to store both the old and the new state in the same cell.
Each cell holds one bit of state but is stored as an int, leaving room to record a transition instead of a single value. Use four numbers to represent the four possible transitions:
0 -> was dead, stays dead (dead -> dead)1 -> was alive, stays alive (alive -> alive)2 -> was dead, now alive (dead -> alive)3 -> was alive, now dead (alive -> dead)The original state is recoverable as value % 2: 0 % 2 = 0 (dead), 1 % 2 = 1 (alive), 2 % 2 = 0 (dead), 3 % 2 = 1 (alive). The two even codes mean "was dead" and the two odd codes mean "was alive."
After the first pass encodes all transitions, a second pass converts to final states: 0 and 3 become 0 (dead), 1 and 2 become 1 (alive).
The simultaneous-update requirement holds because neighbor counts always reflect original states. When a cell is processed, some of its neighbors are still raw (0 or 1) and others were already encoded (2 or 3). Reading every neighbor as value % 2 recovers its original bit in both cases (2 % 2 = 0, 3 % 2 = 1), so an encoded neighbor counts the same as it would have before encoding. The encoding never changes a cell's parity until the second pass, which only runs after every count is finished.
board[r][c] % 2 to get the original state (this works even for already-encoded cells).This is already optimal at O(m * n) time and O(1) space. The next approach reaches the same bounds with a more systematic encoding: instead of four chosen integers, it stores the next state in a separate bit of each cell.
Use the bits of each integer directly. Bit 0 (the least significant bit) holds the original state, which is already there. Bit 1 holds the next state, which we compute and set.
To read the original state of any cell, mask with board[r][c] & 1. To set the next state to alive, set bit 1 with board[r][c] |= 2. After processing every cell, right-shift each value by 1 so the next state becomes the current state.
Unlike Approach 2, the two states live in fixed, separate bits rather than four chosen integers, so reading the original state never depends on the modulo trick.
board[r][c] & 1 (reads only the original state from bit 0).board[i][j] |= 2.board[i][j] >>= 1.