289. 生命游戏

生命游戏
根据 百度百科 , 生命游戏 ,简称为 生命 ,是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机。

给定一个包含 m × n 个格子的面板,每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态: 1 即为 活细胞 (live),或 0 即为 死细胞 (dead)。每个细胞与其八个相邻位置(水平,垂直,对角线)的细胞都遵循以下四条生存定律:

  1. 如果活细胞周围八个位置的活细胞数少于两个,则该位置活细胞死亡;

  2. 如果活细胞周围八个位置有两个或三个活细胞,则该位置活细胞仍然存活;

  3. 如果活细胞周围八个位置有超过三个活细胞,则该位置活细胞死亡;

  4. 如果死细胞周围正好有三个活细胞,则该位置死细胞复活;

下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的,其中细胞的出生和死亡是 同时 发生的。给你 m x n 网格面板 board 的当前状态,返回下一个状态。

给定当前 board 的状态,更新 board 到下一个状态。

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

示例 1:
image

输入: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:
image

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

思路:

这个问题的关键是如何在不影响其他细胞状态的情况下,计算每个细胞的新状态。我们需要采取一种策略,在计算过程中避免直接修改当前状态,造成无法预料的影响。

为了实现这个思路,通常有两个步骤:

  1. 先计算出所有细胞的未来状态,但不立即更新棋盘的状态,而是用一个临时值来标记细胞的未来状态。
  2. 遍历整个棋盘,最终更新所有细胞的状态

在代码中,我们用 -12 作为临时标记来表示将要改变的状态:

  • -1:表示当前是活细胞(1),但是在下一代将会死。
  • 2:表示当前是死细胞(0),但是在下一代将会复活。

详细解释代码思路

  1. 邻居的遍历
    • 每个细胞有 8 个邻居。为了计算一个细胞周围的活邻居数量,我们可以遍历该细胞周围的 8 个方向,统计有多少个邻居是活细胞。
    • 我们用 neighbors = {-1, 0, 1} 来表示相邻的位置,分别表示上、下、左、右以及四个对角线方向。通过遍历这些方向,可以获取每个细胞的 8 个邻居的坐标。
  2. 规则判断和临时标记
    • 对于每个细胞,首先计算它周围活细胞的数量 liveNeighbors
    • 然后,根据以下规则来决定细胞的下一代状态:
      • 规则 1:如果细胞当前是活的(board[row][col] == 1),且它周围的活邻居小于 2 或者大于 3(即“孤独”或者“过度拥挤”),则该细胞会死。因此,我们将其标记为 -1,表示它将会死。
      • 规则 2:如果细胞当前是死的(board[row][col] == 0),且它周围有恰好 3 个活邻居(即“繁殖”),则该细胞将复活。因此,我们将其标记为 2,表示它将会活。
      • 规则 3:如果细胞当前是活的,并且它周围的活邻居数量正好为 2 或 3,那么它将继续存活,因此不需要标记。
  3. 更新棋盘状态
    • 一旦我们遍历完所有的细胞并根据规则标记了它们的状态,接下来就要更新棋盘了。
    • 在这一阶段,我们将所有 -1 转换为 0(死细胞),将所有 2 转换为 1(活细胞)。这样,我们就完成了所有细胞的状态更新。

步骤总结

  1. 遍历每个细胞,并计算该细胞周围的活邻居数量。
  2. 根据生命游戏规则,对每个细胞做出改变,使用临时标记 -12 来表示即将发生的状态变化。
  3. 遍历棋盘一次,将临时标记转换为实际的状态(即 -1 转换为 02 转换为 1)。

优点与难点

  • 避免直接修改原始状态:通过使用临时标记(-12),避免了直接修改原始状态对其他细胞产生不必要的影响。
  • 时间复杂度:该算法的时间复杂度是 O(m * n),其中 mn 分别是棋盘的行数和列数,因为每个细胞都需要被访问一次,且每个细胞有固定的邻居数量(8个)。
class Solution {
    public void gameOfLife(int[][] board) {
        int[] neighbors = {-1, 0, 1};
        int rows = board.length;
        int cols = board[0].length;

        for(int row = 0; row < rows; row++){
            for(int col = 0; col < cols; col++){
                // 对于每一个细胞统计其八个相邻位置里的活细胞数量
                int liveNeighbors = 0;
                for(int i = 0; i < 3; i++){
                    for(int j = 0; j <3; j++){
                        //避免自己本身的细胞被计数
                        if(!(neighbors[i] == 0 && neighbors[j] == 0)){
                            //相邻位置的坐标
                            //当 i = -1 和 j = 1 时,r = row - 1,c = col + 1,即左上方的邻居。
                            //当 i = 1 和 j = 0 时,r = row + 1,c = col,即下方的邻居。
                            int r = (row + neighbors[i]);
                            int c = (col + neighbors[j]);

                            //查看相邻的细胞是否是活细胞 + 边界检查
                            if((r < rows && r >= 0) && (c < cols && c >= 0) && (Math.abs(board[r][c]) == 1)){
                                liveNeighbors++;
                            }
                        }
                    }
                }

                //规则1 或 规则3
                if((board[row][col] == 1) && (liveNeighbors < 2 || liveNeighbors > 3)){
                    //-1表示过去是活的,现在是死的
                    board[row][col] = -1;
                }
                //规则4
                if(board[row][col] == 0 && liveNeighbors == 3){
                    //2表示过去是死的,现在是活的
                    board[row][col] = 2;
                }
            }
        }
        //整体遍历一遍更新状态
        for(int row = 0; row < rows; row++){
            for(int col = 0; col < cols; col++){
                if(board[row][col] > 0){
                    board[row][col] = 1;
                }else{
                    board[row][col] = 0;
                }
            }
        }
    }
}
posted @ 2024-12-18 21:28  Drunker•L  阅读(8)  评论(0编辑  收藏  举报