According to Wikipedia's article: "The Game of Life, also known simply as Life, is a cellular automaton devised by the British mathematician John Horton Conway in 1970."
The board is made up of an m x n
grid of cells, where each cell has an initial state: live (represented by a 1
) or dead (represented by a 0
). Each cell interacts with its eight neighbors (horizontal, vertical, diagonal) using the following four rules (taken from the above Wikipedia article):
The next state of the board is determined by applying the above rules simultaneously to every cell in the current state of the m x n
grid board
. In this process, births and deaths occur simultaneously.
Given the current state of the board
, update the board
to reflect its next state.
Note that you do not need to return anything.
Example 1:
Input: board = [[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]]
Example 2:
Input: board = [[1,1],[1,0]] Output: [[1,1],[1,1]]
Constraints:
m == board.length
n == board[i].length
1 <= m, n <= 25
board[i][j]
is 0
or 1
.Follow up:
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
We need to figure out how cells in a grid change from alive to dead or dead to alive based on their neighbors. The brute force way is to look at each cell individually and directly apply the rules based on all its neighbors in the current state.
Here's how the algorithm would work step-by-step:
def game_of_life_brute_force(board):
rows = len(board)
cols = len(board[0])
# Create a copy to store the next state
next_generation_board = [([0] * cols) for _ in range(rows)]
for row in range(rows):
for col in range(cols):
# Count live neighbors for each cell
live_neighbors_count = count_live_neighbors(board, row, col)
# Apply game rules to determine next state
if board[row][col] == 1:
# Cell is alive
if live_neighbors_count < 2 or live_neighbors_count > 3:
next_generation_board[row][col] = 0
else:
next_generation_board[row][col] = 1
else:
# Cell is dead
if live_neighbors_count == 3:
# Reproduction: dead cell becomes alive
next_generation_board[row][col] = 1
else:
next_generation_board[row][col] = 0
# Update the original board
for row in range(rows):
for col in range(cols):
board[row][col] = next_generation_board[row][col]
def count_live_neighbors(board, cell_row, cell_col):
rows = len(board)
cols = len(board[0])
live_neighbors = 0
for i in range(max(0, cell_row - 1), min(rows, cell_row + 2)):
for j in range(max(0, cell_col - 1), min(cols, cell_col + 2)):
if (i, j) != (cell_row, cell_col):
live_neighbors += board[i][j]
return live_neighbors
The Game of Life involves updating a grid of cells based on their neighbors. The optimal approach avoids modifying the original grid directly during the update, ensuring that each cell's new state is determined by its neighbors' *original* states. This prevents the changes from one cell from affecting the update of its neighbors in the same generation.
Here's how the algorithm would work step-by-step:
def game_of_life(board, number_of_generations):
rows = len(board)
cols = len(board[0])
for _ in range(number_of_generations):
# Create a copy to store the next generation.
next_generation_board = [
[0] * cols for _ in range(rows)
]
for row in range(rows):
for col in range(cols):
live_neighbors = 0
for i in range(max(0, row - 1), min(rows, row + 2)):
for j in range(max(0, col - 1), min(cols, col + 2)):
if (i, j) != (row, col):
live_neighbors += board[i][j]
# Apply the Game of Life rules.
if board[row][col] == 1:
# Live cell.
if live_neighbors < 2 or live_neighbors > 3:
next_generation_board[row][col] = 0
else:
next_generation_board[row][col] = 1
else:
# Dead cell.
if live_neighbors == 3:
next_generation_board[row][col] = 1
else:
next_generation_board[row][col] = 0
# Update the original board after processing all cells.
for row in range(rows):
for col in range(cols):
board[row][col] = next_generation_board[row][col]
return board
Case | How to Handle |
---|---|
Null or empty board | Return immediately without modification, or throw an IllegalArgumentException depending on the requirements. |
1x1 board | The single cell will either die or stay alive based on its initial state, and always have zero neighbors. |
1xn or nx1 board (single row or column) | Handle neighbor calculations carefully, considering only the cells to the left/right or top/bottom respectively. |
Board with all live cells | All cells except those on corners and edges will die in the next generation; handle neighbor counts to update in-place. |
Board with all dead cells | Only cells with exactly three live neighbors could become alive in the next generation; otherwise the board remains unchanged. |
Large board (potential memory issues) | In-place modification avoids creating a copy of the board to conserve memory; however, very large boards could still exceed memory limits. |
Board with oscillating patterns (e.g., blinker) | The algorithm should correctly simulate these patterns and converge to the next state without infinite loops. |
Integer overflow in neighbor count | Since the maximum number of neighbors is 8, an integer is sufficient and no overflow will occur. |