289. Game of Life

Given a board with m by n cells, each cell has an initial state live (1) or dead (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.
Write a function to compute the next state (after one update) of the board given its current state.

这是一道medium的题,所以简单的做法很好通过。要么牺牲空间,新开一个二维数组,直接暴力循环后找出每一个值,要么牺牲点时间,保证in-place, 用不同的标记代表0和1的变化,同时不影响后续的统计。

方法一:暴力法,in-place

这是我在in-place要求下的直接做法,如果0->1,我用3表示,1->0,我用2表示,所以在后续统计的时候不仅要看1还要看2的个数,程序非常直白无脑,2和3的选择也是随意的结果,代码如下:

void gameOfLife(vector<vector<int>>& board) {
    int row = board.size();
    if (row == 0) return;
    int col = board[0].size();
    int i, j, count;
    for (i = 0; i < row; i++) {
        for (j = 0; j < col; j++) {
            count = 0;
            // search the 8 neighbors
            if (i - 1 >= 0 && j - 1 >= 0)
                if (board[i - 1][j - 1] == 1 || board[i - 1][j - 1] == 2) count++;
            if (i - 1 >= 0)
                if (board[i - 1][j] == 1 || board[i - 1][j] == 2) count++;
            if (i - 1 >= 0 && j + 1 < col)
                if (board[i - 1][j + 1] == 1 || board[i - 1][j + 1] == 2) count++;
            if (j - 1 >= 0)
                if (board[i][j - 1] == 1 || board[i][j - 1] == 2) count++;
            if (j + 1 < col)
                if (board[i][j + 1] == 1 || board[i][j + 1] == 2) count++;
            if (i + 1 < row && j - 1 >= 0)
                if (board[i + 1][j - 1] == 1 || board[i + 1][j - 1] == 2) count++;
            if (i + 1 < row)
                if (board[i + 1][j] == 1 || board[i + 1][j] == 2) count++;
            if (i + 1 < row && j + 1 < col)
                if (board[i + 1][j + 1] == 1 || board[i + 1][j + 1] == 2) count++;
            // judge and change
            if (board[i][j] == 0) {
                if (count == 3) board[i][j] = 3;
            }
            if (board[i][j] == 1) {
                if (count < 2) board[i][j] = 2;
                if (count > 3) board[i][j] = 2;
            }
        }
    }
    // search again and re-write matrix by my labels
    for (i = 0; i < row; i++) {
        for (j = 0; j < col; j++) {
            if (board[i][j] == 2) board[i][j] = 0;
            if (board[i][j] == 3) board[i][j] = 1;
        }
    }
    return;
}
方法二:bit操作,新的视角

这个做法其实跟我的做法几乎一个思路,但是有了很多改进,不仅是写法,更重要的是操作上。首先我挨个判断8领域的1的个数,他用循环代替,减少了很多判断,也精简了很多。而且,我的2和3只是作为label存在,即使换了别的数字也可以,他的做法里是利用位运算来解决,同样是2和3的问题,逼格完全不一样。
仔细来说可以这样:

[2nd bit, 1st bit] = [next state, current state]

  • 00 dead (next) <- dead (current)
  • 01 dead (next) <- live (current)
  • 10 live (next) <- dead (current)
  • 11 live (next) <- live (current)
    For each cell’s 1st bit, check the 8 pixels around itself, and set the cell’s 2nd bit.

Transition 01 -> 11: when board == 1 and lives >= 2 && lives <= 3.
Transition 00 -> 10: when board == 0 and lives == 3.
To get the current state, simply do
board[i][j] & 1
To get the next state, simply do
board[i][j] >> 1

我们用2bit代表前后的状态,高位是下一个状态,所以一开始整个matrix都是00和01,也就是默认下一个状态为0,我们需要判断哪些下一个状态可以是1,然后高位置1即可,这个操作直接‘或’上10就可以了。而扫描完第一遍后,直接所有右移一位就可以。在判断哪些可以是下一个状态为1的时候,00要求周围3个1,01要求2或者3,所以可以直接要求该位置数字‘或’3 还是3就可以了。另外,因为循环的时候会把本身位置的1算进去,所以count的起始可以是-board[i][j]. 这些小细节会省去很多繁琐的判断。代码如下:

void gameOfLife(vector<vector<int>>& board) {
    int row = board.size();
    int col = row? board[0].size() : 0;
    int i, j, tempi, tempj, count;
    for (i = 0; i < row; i++) {
        for (j = 0; j < col; j++) {
            count = -board[i][j];//避免把本身的1算进去
            for (tempi = max(0, i - 1); tempi < min(i + 2, row); tempi++)
                for (tempj = max(0, j - 1); tempj < min(j + 2, col); tempj++)
                    count += board[tempi][tempj] & 1;//判断现有状态
            if ((count | board[i][j]) == 3)  
                board[i][j] |= 2; //符合条件直接或上2
        }
    }
    for (i = 0; i < row; i++) {
        for (j = 0; j < col; j++) {
            board[i][j] >>= 1; //得到下一个状态
        }
    }
}
方法三:表优化

最近经常遇到表优化,这一题也不例外。优化程序主要有两点:

  • 尽量减少嵌套的循环;
  • 减少对内存的读写操作。

所以这里为了减少对每个点统计周围领域的1的个数带来的内部循环,我们可以直接将每个pattern的结果制成表然后查表,毕竟加上自身和领域一共9位,一共512种可能,也就是512个pattern,每个对应固定的结果,我们将每个pattern的值称之为每个中心点的environment值。

n8 n5 n2 a
n7 n4 n1 b
n6 n3 n0 c

每个代表九位二进制里的位置,以n4为中心,则环境值environment = n8 * 256 + n7 * 128 + n6 * 64 + n5 * 32 + n4 * 16 + n3 * 8 + n2 * 4 + n1 * 2 + n0 * 1,这么做的好处是把每一个格子的死活信息都用一个bit来表示,更巧妙地是当我们计算以n1为中心的环境时,是可以复用这些信息的,我们不用再读取一遍n5, n4, n3, n2, n1, n0的值,直接将上一次的环境值模上64后再乘以8,就是可以将他们都向左平移一格,这时候再读取三个新的值a, b, c就行了。

void gameOfLife(vector<vector<int>>& board) {
    vector<int> lookupTable = {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    int m = board.size();
    int n = board[0].size();
    if (n==0) return;
    vector<vector<int>> buffer(m, vector<int>(n));
    for(int i = 0; i < m; i++){
          // 每一行开始时,先计算初始的环境值(左边两列)
        int environment = (i - 1 >= 0 && board[i - 1][0] == 1? 4 : 0) + (board[i][0] == 
                          1 ? 2 : 0) + (i + 1 < m && board[i + 1][0] == 1 ? 1 : 0);
            // 对该行的每一列,通过加入右边新的一列,来计算该点的环境值
        for(int j = 0; j < n; j++){
                // 将之前的环境值模64再乘以8,然后加上右边新的三列
            environment = (environment % 64) * 8 + (i - 1 >= 0 && j + 1 < n && 
                          board[i - 1][j + 1] == 1 ? 4 : 0) + (j + 1 < n && 
                          board[i][j + 1] == 1 ? 2 : 0) + (i + 1 < m && j + 1 < n && 
                          board[i + 1][j + 1] == 1 ? 1 : 0);
            //buffer存在是因为不能改变现有状态值
            buffer[i][j] = lookupTable[environment];
        }
    }
    for(int i = 0; i < m; i++){
        for(int j = 0; j < n; j++){
            board[i][j] = buffer[i][j];
        }
    }
}

这样的做法理论上速度比起上面两种提高了一倍左右,制表的过程此处略去,可给出Java版参考代码如下:

int[] table = new int[512];
for(int i = 0; i < 512; i++){
    int lives = Integer.bitCount(i);
    if(lives == 3 || (lives - ((i & 16) > 0 ? 1 : 0) == 3)){
        table[i] = 1;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是Game of Life的C++代码示例: ```cpp #include <iostream> #include <vector> #include <chrono> #include <thread> #include <cstdlib> using namespace std; class GameOfLife { private: vector<vector<int>> grid; int rows, columns; public: GameOfLife(int r, int c) : rows(r), columns(c) { grid.resize(rows, vector<int>(columns, 0)); } void initialize() { srand(time(NULL)); for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { grid[i][j] = rand() % 2; } } } int getNeighbourCount(int row, int col) { int count = 0; for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { int r = row + i; int c = col + j; if (r >= 0 && r < rows && c >= 0 && c < columns && !(i == 0 && j == 0)) { count += grid[r][c]; } } } return count; } void nextGeneration() { vector<vector<int>> newGrid(rows, vector<int>(columns, 0)); for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { int neighbourCount = getNeighbourCount(i, j); if (grid[i][j] == 0 && neighbourCount == 3) { newGrid[i][j] = 1; } else if (grid[i][j] == 1 && (neighbourCount == 2 || neighbourCount == 3)) { newGrid[i][j] = 1; } else { newGrid[i][j] = 0; } } } grid = newGrid; } void print() { for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { if (grid[i][j] == 1) { cout << "* "; } else { cout << ". "; } } cout << endl; } } }; int main() { int rows = 40, columns = 40; GameOfLife gol(rows, columns); gol.initialize(); while (true) { gol.print(); gol.nextGeneration(); this_thread::sleep_for(chrono::milliseconds(100)); system("clear"); } return 0; } ``` 此代码实现了一个Game of Life模拟器,它使用随机值初始化单元格,并在每次迭代中计算每个单元格周围的邻居数量,然后使用规则进行更新。最后,它在控制台中打印游戏板并在每次迭代后清除屏幕。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值