Medium

题目描述

根据维基百科的描述:“生命游戏,简称为生命,是英国数学家约翰·霍顿·康威在1970年设计的细胞自动机。”

游戏板由 m x n 个细胞组成,每个细胞都有一个初始状态:活细胞(用 1 表示)或死细胞(用 0 表示)。每个细胞与其八个相邻细胞(水平、垂直、对角线)相互作用,遵循以下四个规则(摘自上述维基百科文章):

  1. 任何活细胞如果邻居中的活细胞少于两个,就会因人口不足而死亡。
  2. 任何活细胞如果邻居中有两个或三个活细胞,就能存活到下一代。
  3. 任何活细胞如果邻居中的活细胞多于三个,就会因人口过多而死亡。
  4. 任何死细胞如果邻居中恰好有三个活细胞,就会因繁殖而成为活细胞。

棋盘的下一个状态是通过将上述规则同时应用于当前状态的 m x n 网格棋盘中的每个细胞来确定的。在这个过程中,出生和死亡是同时发生的。

给定棋盘的当前状态,更新棋盘以反映其下一个状态。

注意你不需要返回任何东西。

示例 1:

输入:board = [[0,1,0],[0,0,1],[1,1,1],[0,0,0]]
输出:[[0,0,0],[1,0,1],[0,1,1],[0,1,0]]

示例 2:

输入:board = [[1,1],[1,0]]
输出:[[1,1],[1,1]]

提示:

  • m == board.length
  • n == board[i].length
  • 1 <= m, n <= 25
  • board[i][j] 是 0 或 1

进阶:

  • 你能否用原地算法解决这个问题?记住棋盘需要同时更新:你不能先更新某些细胞,然后使用它们的更新值去更新其他细胞。
  • 在这个问题中,我们使用 2D 数组来表示棋盘。原则上,棋盘是无限的,这会在活跃区域接近数组边界时引起问题(即活细胞到达边界)。你将如何解决这些问题?

解题思路

这是一道经典的数组模拟题,核心挑战在于需要同时更新所有细胞的状态。

核心思路:

由于需要同时更新所有细胞,我们不能在遍历过程中直接修改数组值,否则会影响后续细胞状态的判断。有两种主要解决方案:

  1. 额外空间法:创建一个新数组存储下一代状态,最后复制回原数组
  2. 原地更新法:使用状态编码,用额外的位来记录下一代状态

推荐解法:原地更新法

我们可以使用状态编码来实现原地更新:

  • 0: 死细胞 → 死细胞
  • 1: 活细胞 → 活细胞
  • 2: 活细胞 → 死细胞
  • 3: 死细胞 → 活细胞

这样在统计邻居时,我们用 board[i][j] % 2 来获取当前状态,同时可以用新的编码值记录下一代状态。

算法步骤:

  1. 遍历每个细胞,统计其活邻居数量
  2. 根据游戏规则确定下一代状态,用编码值记录
  3. 最后遍历数组,将编码值转换为最终状态(除以2取整)

时间复杂度O(mn),空间复杂度O(1)。

代码实现

class Solution {
public:
    void gameOfLife(vector<vector<int>>& board) {
        int m = board.size(), n = board[0].size();
        vector<int> dx = {-1, -1, -1, 0, 0, 1, 1, 1};
        vector<int> dy = {-1, 0, 1, -1, 1, -1, 0, 1};
        
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                int liveCount = 0;
                
                // 统计8个方向的活邻居
                for (int k = 0; k < 8; k++) {
                    int nx = i + dx[k];
                    int ny = j + dy[k];
                    if (nx >= 0 && nx < m && ny >= 0 && ny < n) {
                        liveCount += (board[nx][ny] % 2);
                    }
                }
                
                // 应用游戏规则
                if (board[i][j] == 1) {
                    if (liveCount < 2 || liveCount > 3) {
                        board[i][j] = 2; // 活→死
                    }
                } else {
                    if (liveCount == 3) {
                        board[i][j] = 3; // 死→活
                    }
                }
            }
        }
        
        // 更新为最终状态
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                board[i][j] /= 2;
            }
        }
    }
};
class Solution:
    def gameOfLife(self, board: List[List[int]]) -> None:
        m, n = len(board), len(board[0])
        directions = [(-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)]
        
        for i in range(m):
            for j in range(n):
                live_count = 0
                
                # 统计8个方向的活邻居
                for dx, dy in directions:
                    nx, ny = i + dx, j + dy
                    if 0 <= nx < m and 0 <= ny < n:
                        live_count += (board[nx][ny] % 2)
                
                # 应用游戏规则
                if board[i][j] == 1:
                    if live_count < 2 or live_count > 3:
                        board[i][j] = 2  # 活→死
                else:
                    if live_count == 3:
                        board[i][j] = 3  # 死→活
        
        # 更新为最终状态
        for i in range(m):
            for j in range(n):
                board[i][j] //= 2
public class Solution {
    public void GameOfLife(int[][] board) {
        int m = board.Length, n = board[0].Length;
        int[][] directions = new int[][] {
            new int[]{-1,-1}, new int[]{-1,0}, new int[]{-1,1},
            new int[]{0,-1}, new int[]{0,1},
            new int[]{1,-1}, new int[]{1,0}, new int[]{1,1}
        };
        
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                int liveCount = 0;
                
                // 统计8个方向的活邻居
                foreach (var dir in directions) {
                    int nx = i + dir[0];
                    int ny = j + dir[1];
                    if (nx >= 0 && nx < m && ny >= 0 && ny < n) {
                        liveCount += (board[nx][ny] % 2);
                    }
                }
                
                // 应用游戏规则
                if (board[i][j] == 1) {
                    if (liveCount < 2 || liveCount > 3) {
                        board[i][j] = 2; // 活→死
                    }
                } else {
                    if (liveCount == 3) {
                        board[i][j] = 3; // 死→活
                    }
                }
            }
        }
        
        // 更新为最终状态
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                board[i][j] /= 2;
            }
        }
    }
}
var gameOfLife = function(board) {
    const m = board.length;
    const n = board[0].length;
    
    const directions = [[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]];
    
    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            let liveNeighbors = 0;
            
            for (let [dx, dy] of directions) {
                const newRow = i + dx;
                const newCol = j + dy;
                
                if (newRow >= 0 && newRow < m && newCol >= 0 && newCol < n) {
                    if (board[newRow][newCol] === 1 || board[newRow][newCol] === 2) {
                        liveNeighbors++;
                    }
                }
            }
            
            if (board[i][j] === 1) {
                if (liveNeighbors < 2 || liveNeighbors > 3) {
                    board[i][j] = 2;
                }
            } else {
                if (liveNeighbors === 3) {
                    board[i][j] = 3;
                }
            }
        }
    }
    
    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            if (board[i][j] === 2) {
                board[i][j] = 0;
            } else if (board[i][j] === 3) {
                board[i][j] = 1;
            }
        }
    }
};

复杂度分析

复杂度类型复杂度说明
时间复杂度O(m×n)需要遍历每个细胞两次,每次统计邻居需要常数时间
空间复杂度O(1)只使用了常数级别的额外空间,实现了原地更新

相关题目