HomeLeetcode289. Game of Life - Leetcode Solutions

289. Game of Life – Leetcode Solutions

Description

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):

  1. Any live cell with fewer than two live neighbors dies as if caused by under-population.
  2. Any live cell with two or three live neighbors lives on to the next generation.
  3. Any live cell with more than three live neighbors dies, as if by over-population.
  4. Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.

The next state is created by applying the above rules simultaneously to every cell in the current state, where births and deaths occur simultaneously. Given the current state of the m x n grid board, return the next state.

Examples:

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]]

Solution in Python

Python
class Solution:
    def gameOfLife(self, board: List[List[int]]) -> None:
        """
        Do not return anything, modify board in-place instead.
        """
        
        # Step 1: Define the dimensions of the board
        rows, cols = len(board), len(board[0])
        
        # Step 2: Create a helper function to count live neighbors for each cell
        def count_live_neighbors(r, c):
            # All 8 possible directions (horizontal, vertical, diagonal)
            directions = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]
            live_neighbors = 0
            
            # Check all the surrounding neighbors
            for dr, dc in directions:
                nr, nc = r + dr, c + dc
                # Ensure the neighbor is within bounds and check if it is currently live
                # We use abs(board[nr][nc]) to also account for transitional states
                if 0 <= nr < rows and 0 <= nc < cols and abs(board[nr][nc]) == 1:
                    live_neighbors += 1
            return live_neighbors
        
        # Step 3: Iterate through each cell and apply the rules
        for r in range(rows):
            for c in range(cols):
                # Count live neighbors
                live_neighbors = count_live_neighbors(r, c)
                
                # Rule 1: Any live cell with fewer than two live neighbors dies (underpopulation)
                # Rule 2: Any live cell with two or three live neighbors stays alive
                # Rule 3: Any live cell with more than three live neighbors dies (overpopulation)
                if board[r][c] == 1:
                    if live_neighbors < 2 or live_neighbors > 3:
                        # Mark cell as dead in the next state, but it's currently live (1 -> -1)
                        board[r][c] = -1
                        
                # Rule 4: Any dead cell with exactly three live neighbors becomes a live cell
                elif board[r][c] == 0:
                    if live_neighbors == 3:
                        # Mark cell as live in the next state, but it's currently dead (0 -> 2)
                        board[r][c] = 2

        # Step 4: Final pass to update the board to the new state
        for r in range(rows):
            for c in range(cols):
                # If the cell was marked as -1, it means it was live and is now dead
                if board[r][c] == -1:
                    board[r][c] = 0
                # If the cell was marked as 2, it means it was dead and is now live
                elif board[r][c] == 2:
                    board[r][c] = 1

Explanation:

  1. State Transition Encoding:
    • To handle the in-place update, we use intermediate states to avoid conflicts when counting neighbors:
      • 1-1: The cell is currently live but will die.
      • 02: The cell is currently dead but will become live.
  2. Count Live Neighbors:
    • We define a helper function count_live_neighbors to count how many live neighbors each cell has. This function checks the 8 possible directions around a cell (top-left, top, top-right, left, right, bottom-left, bottom, bottom-right).
  3. Rules Implementation:
    • For each cell, we apply the Game of Life rules:
      • If the cell is live and has fewer than 2 or more than 3 live neighbors, it dies.
      • If the cell is dead and has exactly 3 live neighbors, it becomes live.
  4. Final Update:
    • After marking all the cells with transitional states (-1 for live-to-dead, 2 for dead-to-live), we go through the board one more time and finalize the transitions:
      • Convert -1 to 0 (dead).
      • Convert 2 to 1 (live).

Solution in C++

C++
class Solution {
public:
    void gameOfLife(vector<vector<int>>& board) {
        // Step 1: Get the dimensions of the board
        int rows = board.size();
        int cols = board[0].size();
        
        // Step 2: Create a helper function to count live neighbors for each cell
        auto countLiveNeighbors = [&](int r, int c) -> int {
            // All 8 possible directions (horizontal, vertical, diagonal)
            vector<pair<int, int>> directions = {{-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}};
            int liveNeighbors = 0;

            // Check all surrounding neighbors
            for (auto dir : directions) {
                int nr = r + dir.first;
                int nc = c + dir.second;
                
                // Ensure the neighbor is within bounds and count if it's live
                // We use abs(board[nr][nc]) to also account for transitional states
                if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && abs(board[nr][nc]) == 1) {
                    liveNeighbors++;
                }
            }
            return liveNeighbors;
        };
        
        // Step 3: Iterate through each cell and apply the rules
        for (int r = 0; r < rows; ++r) {
            for (int c = 0; c < cols; ++c) {
                // Count live neighbors
                int liveNeighbors = countLiveNeighbors(r, c);

                // Rule 1 & 3: Any live cell with fewer than 2 or more than 3 live neighbors dies
                if (board[r][c] == 1 && (liveNeighbors < 2 || liveNeighbors > 3)) {
                    // Mark cell as dead in the next state, but it's currently live (1 -> -1)
                    board[r][c] = -1;
                }

                // Rule 4: Any dead cell with exactly 3 live neighbors becomes a live cell
                if (board[r][c] == 0 && liveNeighbors == 3) {
                    // Mark cell as live in the next state, but it's currently dead (0 -> 2)
                    board[r][c] = 2;
                }
            }
        }

        // Step 4: Final pass to update the board to the new state
        for (int r = 0; r < rows; ++r) {
            for (int c = 0; c < cols; ++c) {
                // If the cell was marked as -1, it means it was live and is now dead
                if (board[r][c] == -1) {
                    board[r][c] = 0;
                }
                // If the cell was marked as 2, it means it was dead and is now live
                else if (board[r][c] == 2) {
                    board[r][c] = 1;
                }
            }
        }
    }
};

Solution in Javascript

JavaScript
var gameOfLife = function(board) {
    // Step 1: Get the dimensions of the board
    const rows = board.length;
    const cols = board[0].length;

    // Step 2: Helper function to count live neighbors for a given cell
    const countLiveNeighbors = (r, c) => {
        // All 8 possible directions (horizontal, vertical, diagonal)
        const directions = [
            [-1, -1], [-1, 0], [-1, 1], 
            [0, -1],         [0, 1], 
            [1, -1], [1, 0], [1, 1]
        ];
        let liveNeighbors = 0;

        // Check all 8 directions around the cell (r, c)
        for (const [dr, dc] of directions) {
            const nr = r + dr;
            const nc = c + dc;

            // Make sure the neighboring cell is within bounds
            // We use Math.abs(board[nr][nc]) to check if a cell was originally alive (1 or -1)
            if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && Math.abs(board[nr][nc]) === 1) {
                liveNeighbors++;
            }
        }
        return liveNeighbors;
    };

    // Step 3: Iterate through each cell and apply the rules
    for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
            // Count live neighbors for the current cell
            const liveNeighbors = countLiveNeighbors(r, c);

            // Rule 1 & 3: Any live cell with fewer than 2 or more than 3 live neighbors dies
            if (board[r][c] === 1 && (liveNeighbors < 2 || liveNeighbors > 3)) {
                // Mark the cell as dead in the next state (live -> dead, 1 -> -1)
                board[r][c] = -1;
            }

            // Rule 4: Any dead cell with exactly 3 live neighbors becomes a live cell
            if (board[r][c] === 0 && liveNeighbors === 3) {
                // Mark the cell as live in the next state (dead -> live, 0 -> 2)
                board[r][c] = 2;
            }
        }
    }

    // Step 4: Update the board to the new state
    for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
            // If the cell was marked as -1, it means it was live and is now dead
            if (board[r][c] === -1) {
                board[r][c] = 0;
            }
            // If the cell was marked as 2, it means it was dead and is now live
            if (board[r][c] === 2) {
                board[r][c] = 1;
            }
        }
    }
};

Solution in Java

Java
class Solution {
    public void gameOfLife(int[][] board) {
        // Step 1: Get the dimensions of the board
        int rows = board.length;
        int cols = board[0].length;

        // Step 3: Apply the rules for each cell
        for (int r = 0; r < rows; r++) {
            for (int c = 0; c < cols; c++) {
                // Count the live neighbors for the current cell
                int liveNeighbors = countLiveNeighbors(board, r, c);

                // Rule 1 & 3: Any live cell with fewer than 2 or more than 3 live neighbors dies
                if (board[r][c] == 1 && (liveNeighbors < 2 || liveNeighbors > 3)) {
                    // Mark the cell as dead in the next state (live -> dead, 1 -> -1)
                    board[r][c] = -1;
                }

                // Rule 4: Any dead cell with exactly 3 live neighbors becomes live
                if (board[r][c] == 0 && liveNeighbors == 3) {
                    // Mark the cell as live in the next state (dead -> live, 0 -> 2)
                    board[r][c] = 2;
                }
            }
        }

        // Step 4: Final pass to update the board to the new state
        for (int r = 0; r < rows; r++) {
            for (int c = 0; c < cols; c++) {
                // If the cell was marked as -1, it means it was live and is now dead
                if (board[r][c] == -1) {
                    board[r][c] = 0;
                }
                // If the cell was marked as 2, it means it was dead and is now live
                if (board[r][c] == 2) {
                    board[r][c] = 1;
                }
            }
        }
    }

    // Step 2: Move the helper function outside the gameOfLife method
    // Helper function to count live neighbors for a given cell
    private int countLiveNeighbors(int[][] board, int r, int c) {
        // All 8 possible directions (horizontal, vertical, diagonal)
        int[][] directions = {
            {-1, -1}, {-1, 0}, {-1, 1}, 
            {0, -1},         {0, 1}, 
            {1, -1}, {1, 0}, {1, 1}
        };
        int liveNeighbors = 0;
        int rows = board.length;
        int cols = board[0].length;

        // Check all 8 surrounding neighbors
        for (int[] direction : directions) {
            int nr = r + direction[0];
            int nc = c + direction[1];

            // Ensure the neighbor is within bounds
            // Use Math.abs(board[nr][nc]) to check if it was originally live (1 or -1)
            if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && Math.abs(board[nr][nc]) == 1) {
                liveNeighbors++;
            }
        }
        return liveNeighbors;
    }
}

Subscribe
Notify of

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments

Popular