题目大意
根据 百度百科 ,生命游戏,简称为生命,是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机。
给定一个包含 m × n 个格子的面板,每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态:1 即为活细胞(live),或 0 即为死细胞(dead)。每个细胞与其八个相邻位置(水平,垂直,对角线)的细胞都遵循以下四条生存定律:
- 如果活细胞周围八个位置的活细胞数少于两个,则该位置活细胞死亡;
- 如果活细胞周围八个位置有两个或三个活细胞,则该位置活细胞仍然存活;
- 如果活细胞周围八个位置有超过三个活细胞,则该位置活细胞死亡;
- 如果死细胞周围正好有三个活细胞,则该位置死细胞复活;
根据当前状态,写一个函数来计算面板上所有细胞的下一个(一次更新后的)状态。下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的,其中细胞的出生和死亡是同时发生的。
示例:
输入:
[
[0,1,0],
[0,0,1],
[1,1,1],
[0,0,0]
]
输出:
[
[0,0,0],
[1,0,1],
[0,1,1],
[0,1,0]
]
你可以使用原地算法解决本题吗?请注意,面板上所有格子需要同时被更新:你不能先更新某些格子,然后使用它们的更新后的值再更新其他格子。
本题中,我们使用二维数组来表示面板。原则上,面板是无限的,但当活细胞侵占了面板边界时会造成问题。你将如何解决这些问题?
解题思路
对于活细胞,用1表示,对于死细胞,用0表示,一个整数可以用8个二进制位表示,0和1仅占用了第一位,因此我们可以考虑使用二进制来表示。我们用二进制的第一位表示当前细胞状态,二进制的第二位表示下一轮的细胞状态。
- 10:当前是死细胞,下一轮是活细胞;
- 11:当前是活细胞,下一轮是活细胞;
- 00:当前是死细胞,下一轮是死细胞;
- 01:当前是活细胞,下一轮是死细胞;
- ps:之所以要记录下一轮状态,是因为如果直接对其进行修改,则会影响本轮的其它细胞结果;
有了上述表示之后,我们就可以使用额外空间复杂度为1的原地算法了。
首先统计一下当前细胞周围的8个位置活细胞的数量。
如果当前是活细胞:
- 周围活细胞数量为2或者3,表示下一轮这个细胞仍然存活,因此board[i][j]=11;
- 否则,下一轮这个细胞死亡,board[i][j]=01;
如果当前是死细胞: - 周围活细胞数量为3,下一轮变成活细胞,board[i][j]=10;
- 否则board[i][j]=00;
当遍历完所有位置后,去掉当前的状态,即变成了下一个阶段的状态:
例如board[i][j]=10,表示当前是死细胞,下一个状态是活细胞,统一处理为board[i][j]>>=1,就能得到board[i][j]=1。
class Solution {
private:
vector<int> X = {-1, -1, -1, 0, 0, 1, 1, 1}, Y = {-1, 0, 1, -1, 1, -1, 0, 1};
public:
void gameOfLife(vector<vector<int>>& board) {
int row = board.size(), col = board[0].size();
for (int i = 0; i < row; ++i){
for (int j = 0; j < col; ++j){
int live = 0;
// 记录当前位置周围的活细胞数量
for (int k = 0; k < 8; ++k){
if (i + X[k] < 0 || i + X[k] >= row || j + Y[k] < 0 || j + Y[k] >= col)
continue;
live += (board[i][j] % 10);
}
// 当前位置是活细胞
if ((board[i][j] & 1) > 0){
if (live == 2 || live == 3)
board[i][j] = 0b11;
else
board[i][j] = 0b01;
}
// 当前位置是死细胞
else{
if (live == 3)
board[i][j] = 0b10;
else
board[i][j] = 0b00;
}
}
}
// 去掉当前的状态
for (int i = 0; i < row; ++i){
for (int j = 0; j < col; ++j){
board[i][j] >>= 1;
}
}
}
};