289.生命游戏
根据 百度百科 ,生命游戏 ,简称为 生命 ,是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机。
给定一个包含 m × n 个格子的面板,每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态: 1 即为 活细胞 (live),或 0 即为 死细胞 (dead)。每个细胞与其八个相邻位置(水平,垂直,对角线)的细胞都遵循以下四条生存定律:
- 如果活细胞周围八个位置的活细胞数少于两个,则该位置活细胞死亡;
- 如果活细胞周围八个位置有两个或三个活细胞,则该位置活细胞仍然存活;
- 如果活细胞周围八个位置有超过三个活细胞,则该位置活细胞死亡;
- 如果死细胞周围正好有三个活细胞,则该位置死细胞复活;
下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的,其中细胞的出生和死亡是同时发生的。给你 m x n 网格面板 board 的当前状态,返回下一个状态。
思路:
使用一个复制数组作为参考
为何使用复制数组
-
计算正确性:
- 在《生命游戏》中,每个细胞的下一状态(活或死)依赖于当前状态的邻居细胞。这意味着,如果在遍历原始数组期间直接修改细胞的状态,可能会影响对邻居的计算。
- 例如,如果当前细胞的状态取决于周围某个细胞,而这个周围的细胞又在同一时刻被更新了(例如,被设置为活或死),这样会导致错误的计算结果。
-
分离计算过程:
- 复制数组允许你在遍历原始数组时,使用一个稳定的参考状态。这样,无论你如何修改原始数组,其邻居状态始终基于该复制数组。
-
简化逻辑:
- 使用一个复制数组简化了状态更新逻辑。你可以清晰地分开读取状态和修改状态的过程,避免了复杂的临界条件判断,保持代码的可读性和可维护性。
举个例子
假设有一个简单的游戏板如下(1
表示活细胞,0
表示死细胞):
0 1 0
1 1 1
0 0 0
在某一时刻:
- 在计算位置 (1, 1) 的活邻居时,它的邻居是:
- (0, 0) [0]
- (0, 1) [1]
- (0, 2) [0]
- (1, 0) [1]
- (1, 2) [1]
- (2, 0) [0]
- (2, 1) [0]
- (2, 2) [0]
其下一状态应该是 1
(仍然是活的),因为它有 3 个活邻居。
但是如果此时你直接在原始 board
中修改了一些细胞的状态,比如把 (1, 0)
的状态改为死细胞,那么在计算 (1, 1) 时,活邻居的数量可能会因为你的修改而不是 3,导致错误的结果。因此,使用 copyBoard
来存储原始状态是必要的。
总结
- 通过使用一个复制数组,确保计算的准确性和逻辑的简单性,从而实现稳定、正确的细胞状态更新。这样能够避免直接在遍历过程中修改原始数据所带来的复杂性和潜在错误,是一种非常常见的编程技巧。
class Solution {
public void gameOfLife(int[][] board) { // 定义游戏的主函数,输入参数为二维数组 'board',表示细胞的状态
int[] neighbors = {0, 1, -1}; // 创建数组 'neighbors' 用于表示细胞相对于自身的偏移量,以便计算邻居
int rows = board.length; // 获取 'board' 的行数
int cols = board[0].length; // 获取 'board' 的列数
int[][] copyBoard = new int[rows][cols]; // 创建一个与 'board' 相同大小的副本数组,用于存储当前状态
// 循环遍历每个细胞,将 'board' 的状态复制到 'copyBoard'
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
copyBoard[i][j] = board[i][j]; // 将当前细胞的状态从 'board' 复制到 'copyBoard'
}
}
// 再次遍历每个细胞以计算下一状态
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
int live = 0; // 初始化活邻居计数
// 遍历该细胞周围的 8 个邻居
for (int k = 0; k < 3; k++) {
for (int l = 0; l < 3; l++) {
// 排除自身细胞
if (!(neighbors[k] == 0 && neighbors[l] == 0)) {
int r = (i + neighbors[k]); // 计算邻居细胞的行坐标
int c = (j + neighbors[l]); // 计算邻居细胞的列坐标
// 检查邻居坐标是否在边界内,并判断该邻居细胞是否为活细胞
if ((r < rows && r >= 0) && (c < cols && c >= 0) && (copyBoard[r][c] == 1)) {
live += 1; // 增加活邻居计数
}
}
}
}
// 应用规则 1 或规则 3
// 如果当前细胞是活细胞,但活邻居小于 2 或大于 3,则该细胞死去
if ((copyBoard[i][j] == 1) && (live < 2 || live > 3)) {
board[i][j] = 0; // 设置当前细胞为死细胞
}
// 应用规则 4
// 如果当前细胞是死细胞,且活邻居正好是 3,则该细胞复活
if (copyBoard[i][j] == 0 && live == 3) {
board[i][j] = 1; // 设置当前细胞为活细胞
}
}
}
}
}