1. 程式人生 > 實用技巧 >leetcode 精細型top面試題 - 289. 生命遊戲

leetcode 精細型top面試題 - 289. 生命遊戲

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)。