Leetcode Study Day 18

Game of Life

According to Wikipedia’s article: “The Game of Life, also known simply as Life, is a cellular automaton devised by the British mathematician John Horton Conway in 1970.”

The board is made up of an m x n grid of cells, where each cell has an initial state: live (represented by a 1) or dead (represented by a 0). Each cell interacts with its eight neighbors (horizontal, vertical, diagonal) using the following four rules (taken from the above Wikipedia article):

Any live cell with fewer than two live neighbors dies as if caused by under-population.
Any live cell with two or three live neighbors lives on to the next generation.
Any live cell with more than three live neighbors dies, as if by over-population.
Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.
The next state is created by applying the above rules simultaneously to every cell in the current state, where births and deaths occur simultaneously. Given the current state of the m x n grid board, return the next state.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Example 1:


Input: board = [[0,1,0],[0,0,1],[1,1,1],[0,0,0]]
Output: [[0,0,0],[1,0,1],[0,1,1],[0,1,0]]
Example 2:


Input: board = [[1,1],[1,0]]
Output: [[1,1],[1,1]]


Constraints:

m == board.length
n == board[i].length
1 <= m, n <= 25
board[i][j] is 0 or 1.

My solution

My solution is intuitive. I create a new matrix to store the next state of the board. Then I iterate through the board and check the number of live neighbours. Then I apply the rules to the new matrix. Finally, I copy the new matrix to the board.

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
57
58
59
class Solution {
public:

void gameOfLife(vector<vector<int>>& board) {
vector<vector<int>> answer(board.size(), vector<int>(board[0].size(), 0));
for (int i = 0; i < board.size(); i ++){
for (int j = 0; j < board[0].size(); j++){
int count = 0;
// check neighbours
// up-left
if (j!= 0 && i != 0){
int up_left = board[i-1][j-1];
count += up_left;
}
// up
if (i != 0){
int up = board[i-1][j];
count += up;
}
// up-right
if (j != board[0].size()-1 && i != 0)
{int up_right = board[i-1][j+1];
count += up_right;}
// left
if (j != 0)
{int left = board[i][j-1];
count += left;}
// right
if (j != board[0].size() -1)
{int right = board[i][j+1];
count += right;}
// down-left
if (j != 0 && i!= board.size() -1)
{int down_left = board[i+1][j-1];
count += down_left;}
// down
if (i != board.size() - 1)
{int down = board[i+1][j];
count += down;}
// down-right
if (i != board.size() - 1 && j != board[0].size()-1)
{int down_right = board[i+1][j+1];
count += down_right;}
if (board[i][j] == 1 && count < 2){
answer[i][j] = 0;
}
else if (board[i][j] == 1 && count > 3){
answer[i][j] = 0;
}
else if (board[i][j] == 0 && count == 3){
answer[i][j] = 1;
}
else
answer[i][j] = board[i][j];
}
}
board = answer;
}
};

O(1) space, O(mn) time

I found a solution using O(1) space and O(mn) time to solve this question. The reason that he solved it in O(1) space is because he used the second bit to store the next state of the cell.

Let’s break down the code, line by line:

1
int m=board.size(), n=m?board[0].size():0;

We define two integer variables m and n:

  • m stores the number of rows of the board.
  • n stores the number of columns of the board if m is not zero. If m is zero (i.e., board is empty), n is set to zero.
1
2
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){

These nested loops iterate over each cell in the board.

1
int count=0;

This initializes a counter to count the number of live neighbors for the current cell (i, j).

1
2
for(int ss=max(i-1, 0); ss<min(i+2, m); ss++){
for(int tt=max(j-1, 0); tt<min(j+2, n); tt++){

These nested loops iterate over the neighbors of the current cell (i, j). They ensure that the loops do not go out of bounds by using the max and min functions.

1
count+=board[ss][tt]&1;

Here, the code checks the least significant bit of the neighboring cell (ss, tt) to see if it’s alive (1) or dead (0). The operation & 1 when applied to an integer effectively isolates the least significant bit (LSB) of its binary representation. The result is added to the count, which accumulates the number of live neighbors.

1
2
}
count-=board[i][j];

After counting all the live neighbors, we subtract the state of the current cell (i, j) from the count since we included it in our previous loop.

1
2
if(count==3 || (board[i][j]&&count==2))
board[i][j]|=2;

Here, we’re applying the rules of Conway’s Game of Life:

  • If a cell has exactly 3 live neighbors, it becomes alive (or stays alive).
  • If a live cell has exactly 2 live neighbors, it stays alive.

The board[i][j] |= 2; operation sets the second least significant bit of the current cell to 1, indicating that the cell will be alive in the next state.

These close the nested loops iterating over each cell in the board.

1
2
3
for(int i=0; i<m; i++)
for(int j=0; j<n; j++)
board[i][j]>>=1;

These nested loops go over each cell in the board again. For each cell, it right shifts its bits by one position. This is done to update the board to its next state by making the second least significant bit (which represents the next state) the current state.

The cleverness of this implementation lies in its use of bitwise operations to store both the current state and the next state of each cell in the same integer. This way, the algorithm can compute the next state of the board in-place without needing additional memory for a temporary board.

  • Copyrights © 2020-2024 Yangyang Cui

请我喝杯咖啡吧~

支付宝
微信