根据
百度百科
, 生命游戏 ,简称为 生命 ,是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机。
给定一个包含 m × n
个格子的面板,每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态: 1
即为 活细胞 (live),或
0
即为 死细胞 (dead)。每个细胞与其八个相邻位置(水平,垂直,对角线)的细胞都遵循以下四条生存定律:
- 如果活细胞周围八个位置的活细胞数少于两个,则该位置活细胞死亡;
- 如果活细胞周围八个位置有两个或三个活细胞,则该位置活细胞仍然存活;
- 如果活细胞周围八个位置有超过三个活细胞,则该位置活细胞死亡;
- 如果死细胞周围正好有三个活细胞,则该位置死细胞复活;
下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的,其中细胞的出生和死亡是同时发生的。给你 m x n
网格面板 board
的当前状态,返回下一个状态。
示例 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
进阶:
- 你可以使用原地算法解决本题吗?请注意,面板上所有格子需要同时被更新:你不能先更新某些格子,然后使用它们的更新后的值再更新其他格子。
- 本题中,我们使用二维数组来表示面板。原则上,面板是无限的,但当活细胞侵占了面板边界时会造成问题。你将如何解决这些问题?
📺 视频题解
📖 文字题解
分析
在讲具体解法之前,请先根据下面的图片理解题目中描述的细胞遵循的生存定律,这有助于我们后面的讲解。
方法一:复制原数组进行模拟
思路
这个问题看起来很简单,但有一个陷阱,如果你直接根据规则更新原始数组,那么就做不到题目中说的 同步 更新。假设你直接将更新后的细胞状态填入原始数组,那么当前轮次其他细胞状态的更新就会引用到当前轮已更新细胞的状态,但实际上每一轮更新需要依赖上一轮细胞的状态,是不能用这一轮的细胞状态来更新的。
如上图所示,已更新细胞的状态会影响到周围其他还未更新细胞状态的计算。一个最简单的解决方法就是复制一份原始数组,复制的那一份永远不修改,只作为更新规则的引用。这样原始数组的细胞值就不会被污染了。
算法
[solution1-C++]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| class Solution { public: void gameOfLife(vector<vector<int>>& board) { int neighbors[3] = {0, 1, -1};
int rows = board.size(); int cols = board[0].size();
vector<vector<int> >copyBoard(rows, vector<int>(cols, 0));
for (int row = 0; row < rows; row++) { for (int col = 0; col < cols; col++) { copyBoard[row][col] = board[row][col]; } }
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)) { int r = (row + neighbors[i]); int c = (col + neighbors[j]);
if ((r < rows && r >= 0) && (c < cols && c >= 0) && (copyBoard[r][c] == 1)) { liveNeighbors += 1; } } } }
if ((copyBoard[row][col] == 1) && (liveNeighbors < 2 || liveNeighbors > 3)) { board[row][col] = 0; } if (copyBoard[row][col] == 0 && liveNeighbors == 3) { board[row][col] = 1; } } } } };
|
[solution1-Java]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| class Solution { public void gameOfLife(int[][] board) { int[] neighbors = {0, 1, -1};
int rows = board.length; int cols = board[0].length;
int[][] copyBoard = new int[rows][cols];
for (int row = 0; row < rows; row++) { for (int col = 0; col < cols; col++) { copyBoard[row][col] = board[row][col]; } }
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)) { int r = (row + neighbors[i]); int c = (col + neighbors[j]);
if ((r < rows && r >= 0) && (c < cols && c >= 0) && (copyBoard[r][c] == 1)) { liveNeighbors += 1; } } } }
if ((copyBoard[row][col] == 1) && (liveNeighbors < 2 || liveNeighbors > 3)) { board[row][col] = 0; } if (copyBoard[row][col] == 0 && liveNeighbors == 3) { board[row][col] = 1; } } } } }
|
[solution1-Python3]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class Solution: def gameOfLife(self, board: List[List[int]]) -> None: """ Do not return anything, modify board in-place instead. """
neighbors = [(1,0), (1,-1), (0,-1), (-1,-1), (-1,0), (-1,1), (0,1), (1,1)]
rows = len(board) cols = len(board[0])
copy_board = [[board[row][col] for col in range(cols)] for row in range(rows)]
for row in range(rows): for col in range(cols):
live_neighbors = 0 for neighbor in neighbors:
r = (row + neighbor[0]) c = (col + neighbor[1])
if (r < rows and r >= 0) and (c < cols and c >= 0) and (copy_board[r][c] == 1): live_neighbors += 1
if copy_board[row][col] == 1 and (live_neighbors < 2 or live_neighbors > 3): board[row][col] = 0 if copy_board[row][col] == 0 and live_neighbors == 3: board[row][col] = 1
|
复杂度分析
方法二:使用额外的状态
思路
方法一中 $O(mn)$ 的空间复杂度在数组很大的时候内存消耗是非常昂贵的。题目中每个细胞只有两种状态 live(1)
或 dead(0)
,但我们可以拓展一些复合状态使其包含之前的状态。举个例子,如果细胞之前的状态是 0
,但是在更新之后变成了 1
,我们就可以给它定义一个复合状态 2
。这样我们看到 2
,既能知道目前这个细胞是活的,还能知道它之前是死的。
算法
遍历 board
中的细胞。
根据数组的细胞状态计算新一轮的细胞状态,这里会用到能同时代表过去状态和现在状态的复合状态。
具体的计算规则如下所示:
规则 1:如果活细胞周围八个位置的活细胞数少于两个,则该位置活细胞死亡。这时候,将细胞值改为 -1
,代表这个细胞过去是活的现在死了;
规则 2:如果活细胞周围八个位置有两个或三个活细胞,则该位置活细胞仍然存活。这时候不改变细胞的值,仍为 1
;
规则 3:如果活细胞周围八个位置有超过三个活细胞,则该位置活细胞死亡。这时候,将细胞的值改为 -1
,代表这个细胞过去是活的现在死了。可以看到,因为规则 1 和规则 3 下细胞的起始终止状态是一致的,因此它们的复合状态也一致;
规则 4:如果死细胞周围正好有三个活细胞,则该位置死细胞复活。这时候,将细胞的值改为 2
,代表这个细胞过去是死的现在活了。
根据新的规则更新数组;
现在复合状态隐含了过去细胞的状态,所以我们可以在不复制数组的情况下完成原地更新;
对于最终的输出,需要将 board
转成 0
,1
的形式。因此这时候需要再遍历一次数组,将复合状态为 2
的细胞的值改为 1
,复合状态为 -1
的细胞的值改为 0
。
[solution2-C++]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| class Solution { public: void gameOfLife(vector<vector<int>>& board) { int neighbors[3] = {0, 1, -1};
int rows = board.size(); int cols = board[0].size();
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)) { int r = (row + neighbors[i]); int c = (col + neighbors[j]);
if ((r < rows && r >= 0) && (c < cols && c >= 0) && (abs(board[r][c]) == 1)) { liveNeighbors += 1; } } } }
if ((board[row][col] == 1) && (liveNeighbors < 2 || liveNeighbors > 3)) { board[row][col] = -1; } if (board[row][col] == 0 && liveNeighbors == 3) { 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; } } } } };
|
[solution2-Java]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| class Solution { public void gameOfLife(int[][] board) { int[] neighbors = {0, 1, -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)) { 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; } } } }
if ((board[row][col] == 1) && (liveNeighbors < 2 || liveNeighbors > 3)) { board[row][col] = -1; } if (board[row][col] == 0 && liveNeighbors == 3) { 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; } } } } }
|
[solution2-Python3]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| class Solution: def gameOfLife(self, board: List[List[int]]) -> None: """ Do not return anything, modify board in-place instead. """
neighbors = [(1,0), (1,-1), (0,-1), (-1,-1), (-1,0), (-1,1), (0,1), (1,1)]
rows = len(board) cols = len(board[0])
for row in range(rows): for col in range(cols):
live_neighbors = 0 for neighbor in neighbors:
r = (row + neighbor[0]) c = (col + neighbor[1])
if (r < rows and r >= 0) and (c < cols and c >= 0) and abs(board[r][c]) == 1: live_neighbors += 1
if board[row][col] == 1 and (live_neighbors < 2 or live_neighbors > 3): board[row][col] = -1 if board[row][col] == 0 and live_neighbors == 3: board[row][col] = 2
for row in range(rows): for col in range(cols): if board[row][col] > 0: board[row][col] = 1 else: board[row][col] = 0
|
复杂度分析