289. 生命游戏
题目描述
根据 百度百科 ,生命游戏,简称为生命,是英国数学家约翰·何顿·康威在 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] ]
进阶:
你可以使用原地算法解决本题吗?请注意,面板上所有格子需要同时被更新:你不能先更新某些格子,然后使用它们的更新后的值再更新其他格子。
本题中,我们使用二维数组来表示面板。原则上,面板是无限的,但当活细胞侵占了面板边界时会造成问题。你将如何解决这些问题?
思路一:借助中间数组
因为整个矩阵的细胞状态变化是在同时且瞬间完成的,所以我们不能在原有的数组上进行修改,因为当前做的修改,可能会影响将来某个细胞的周围活细胞数目的统计,所以我们拷贝一份新的数组,统计新数组中每个细胞8个位置的活细胞数量,根据活细胞的数量以及当前细胞的存活状态得出当前细胞的下个状态,把下个状态存入原矩阵的相应位置。
1 class Solution { 2 3 int[][] dir = {{-1, 0}, {0, -1}, {-1, 1}, {1, -1}, {-1, -1}, {1, 1}, {1, 0},{0, 1}}; 4 5 // 统计某细胞周围8个位置的活细胞个数 6 public int getLiveCellCount(int row, int col, int rows, int cols, int[][] arr){ 7 int count = 0; 8 for(int i = 0; i < 8; i++){ 9 int newRow = row + dir[i][0]; 10 int newCol = col + dir[i][1]; 11 if(newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols){ 12 count += arr[newRow][newCol]; 13 } 14 } 15 return count; 16 } 17 18 public void gameOfLife(int[][] board) { 19 20 if(board.length == 0 || board[0].length == 0){ 21 return; 22 } 23 // 把结果保存在原数组中 24 int rows = board.length; 25 int cols = board[0].length; 26 int[][] tempBoard = new int[rows][cols]; 27 for(int i = 0; i < rows; i++){ 28 for(int j = 0; j < cols; j++){ 29 tempBoard[i][j] = board[i][j]; 30 } 31 } 32 33 for(int i = 0; i < rows; i++){ 34 for(int j = 0; j < cols; j++){ 35 int liveCellCount = getLiveCellCount(i, j, rows, cols, tempBoard); 36 if(tempBoard[i][j] == 1 && (liveCellCount < 2 || liveCellCount > 3)){ 37 board[i][j] = 0; 38 }else if(tempBoard[i][j] == 1){ 39 board[i][j] = 1; 40 }else if(tempBoard[i][j] == 0 && liveCellCount == 3){ 41 board[i][j] = 1; 42 } 43 } 44 } 45 } 46 }
leetcode 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:36.8 MB, 在所有 Java 提交中击败了80.95%的用户
复杂度分析:
时间复杂度:O(8n * m)。对临时的数组的每个元素都统计了一遍它周围的活细胞数目,所以时间复杂为O(8nm)。
空间复杂度:O(mn)。需要一个一个大小为 m*n 的临时数组,所以空间复杂度为 O(mn)。
思路二:原地改变, 不借助中间数组
思路参考:https://leetcode-cn.com/problems/game-of-life/solution/sheng-ming-you-xi-by-leetcode-solution/
思路一之所以需要借助一个临时数组,是因为整个矩阵的细胞状态变化是在同时且瞬间完成的,所以我们不能在原有的数组上进行修改,因为当前做的修改,可能会影响将来某个细胞的周围活细胞数目的统计,因为无法判断周围的细胞是否被改变过。但是我们如果能判断周围细胞是否被改变过,且知道是改变前的状态的话那就可以在原数组上直接操作。
想法是如果是活细胞变死细胞, 状态不变为0,而是状态变为-1, 活细胞变活细胞,状态不变,死细胞变活细胞,状态不变为1, 而是变为2。
这样将来统计某个细胞周围所有的活细胞个数时,不仅统一状态为1的,还统计状态为-1, 这样就能准确的统计出该细胞周围活细胞的数量了
不过因为状态-1 和2并不是我们最终想要的状态值,所以我们最后还需要把-1和2重新变成0和1, 因为-1是由活细胞变成了死细胞,所以最终状态应该改为0, 2是由死细胞变成的活细胞,所以因为把状态为2的细胞状态变为1
1 class Solution { 2 3 int[][] dir = {{-1, 0}, {0, -1}, {-1, 1}, {1, -1}, {-1, -1}, {1, 1}, {1, 0},{0, 1}}; 4 5 // 统计某细胞周围8个位置的活细胞个数 6 public int getLiveCellCount(int row, int col, int rows, int cols, int[][] arr){ 7 int count = 0; 8 for(int i = 0; i < 8; i++){ 9 int newRow = row + dir[i][0]; 10 int newCol = col + dir[i][1]; 11 if(newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols && 12 (arr[newRow][newCol] == -1 || arr[newRow][newCol] == 1)){ 13 count += 1; 14 } 15 } 16 return count; 17 } 18 19 public void gameOfLife(int[][] board) { 20 21 if(board.length == 0 || board[0].length == 0){ 22 return; 23 } 24 // 把结果保存在原数组中 25 int rows = board.length; 26 int cols = board[0].length; 27 28 for(int i = 0; i < rows; i++){ 29 for(int j = 0; j < cols; j++){ 30 int liveCellCount = getLiveCellCount(i, j, rows, cols, board); 31 if(board[i][j] == 1 && (liveCellCount < 2 || liveCellCount > 3)){ 32 board[i][j] = -1; 33 }else if(board[i][j] == 1){ 34 board[i][j] = 1; 35 }else if(board[i][j] == 0 && liveCellCount == 3){ 36 board[i][j] = 2; 37 } 38 } 39 } 40 41 // 需要把-1和2重新变成0和1 42 for(int i = 0; i < rows; i++){ 43 for(int j = 0; j < cols; j++){ 44 if(board[i][j] == -1){ 45 board[i][j] = 0; 46 }else if(board[i][j] == 2){ 47 board[i][j] = 1; 48 } 49 } 50 } 51 } 52 }
leetcode 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:36.8 MB, 在所有 Java 提交中击败了84.81%的用户
复杂度分析:
时间复杂度:O(9n * m)。对临时的数组的每个元素都统计了一遍它周围的活细胞数目,花费的时间为 8mn, 最后还需要一个双循环来把所有 -1 和 2 置为 0 和 1,花费的时间为O(nm)。所以总时间复杂为O(9nm)。
空间复杂度:O(mn)。需要一个一个大小为 m*n 的临时数组,所以空间复杂度为 O(mn)。