289. Game of Life

来源:互联网 发布:毒理数据 编辑:程序博客网 时间:2024/05/23 00:06

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