每日leetcode-数组-289. 生命游戏

分类:数组-二维数组变换

题目描述:

根据 百度百科 ,生命游戏,简称为生命,是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机。

给定一个包含 m × n 个格子的面板,每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态:1 即为活细胞(live),或 0 即为死细胞(dead)。每个细胞与其八个相邻位置(水平,垂直,对角线)的细胞都遵循以下四条生存定律:

如果活细胞周围八个位置的活细胞数少于两个,则该位置活细胞死亡;
如果活细胞周围八个位置有两个或三个活细胞,则该位置活细胞仍然存活;
如果活细胞周围八个位置有超过三个活细胞,则该位置活细胞死亡;
如果死细胞周围正好有三个活细胞,则该位置死细胞复活;
下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的,其中细胞的出生和死亡是同时发生的。给你 m x n 网格面板 board 的当前状态,返回下一个状态。

 解题思路:

分析:1代表细胞活的, 0代表细胞死的,那么这个位置就四种状态,用【下一个状态,当前状态】表示,最后需要用右移操作更新结果

状态 0: 00 ,死的,下一轮还是死的
状态 1: 01,活的,下一轮死了
状态 2: 10,死的,下一轮活了
状态 3: 11,活的,下一轮继续活着
进一步:下一轮活的可能有两种,也就是要把单元格变为 1

这个活细胞周围八个位置有两个或三个活细胞,下一轮继续活,属于 11
这个细胞本来死的,周围有三个活着的,下一轮复活了,属于 10

那遍历下每个格子看他周围细胞有多少个活细胞就行了,然后更改为状态,那么对于第一种可能,把 board[i][j]设置为 3,对于第二种可能状态设置为 2,设置个高位flag,遍历后面的格子,拿到与他相邻的格子中有多少个 alive的,和 1与一下即可,最后我们把 board[i][j]右移 1位,更新结果



class Solution:
    def gameOfLife(self, board: List[List[int]]) -> None:
        """
        Do not return anything, modify board in-place instead.
        """
        # 特判
        if not board or not board[0]:
            return
        
        m, n = len(board), len(board[0])
        # 遍历
        for i in range(m):
            for j in range(n):
                # 拿到当前位置周围活细胞数量
                cnt = self.count_alive(board, i, j)
                # 1. 活细胞周围八个位置有两个或三个活细胞,下一轮继续活 
                if board[i][j] and cnt in [2, 3]:
                    board[i][j] = 3
                # 2. 死细胞周围有三个活细胞,下一轮复活了
                if not board[i][j] and cnt == 3:
                    board[i][j] = 2
        # 更新结果
        for i in range(m):
            for j in range(n):
                board[i][j] >>= 1  # 右移一位
    
    def count_alive(self, board, i, j):
        m, n = len(board), len(board[0])
        cnt = 0
        directions = [(0, 1), (0, -1), (-1, 0), (1, 0),   # 上下左右的临近位
                      (1, 1), (1, -1), (-1, 1), (-1, -1)]  # 对角线上的临近位
        
        for dx, dy in directions:
            x, y = i + dx, j + dy
            if x < 0 or y < 0 or x == m or y == n:
            # 如果这个位置为 0,代表当前轮是死的,不需要算进去
            # 如果这个位置为 1,代表当前轮是活得,需要算进去
            # 如果这个位置为 2,代表当前轮是死的(状态10,下一轮是活的),不需要算进去
            # 如果这个位置为 3,代表是当前轮是活的(状态11,下一轮也是活的),需要算进去
                continue
            cnt += board[x][y] & 1  # 按位与1
        
        return cnt
     

解法2:

方法很简单暴力,直接统计每个位置的 8 连通分量并根据部落数进行题目所说的判断就好了。

由于一次迭代是同时进行的,所以不能一边统计一边修改数组,这样会影响后面的判断。我的做法是先把输入的面板复制了一份,这样使用原始的 board 去判断部落数,更新放在了新的 board_next 上不会影响之前的 board。最后再把数值复制过来。

题目给了 4 个存活和死亡的判断条件,直接按照这4个条件判断即可。我定义了一个函数liveOrDead()用来判断当前判断的部落应该活还是死,返回结果的解释:0-不变, 1-活下来,2-要死了。

时间复杂度是O(MN),空间复杂度是O(MN).

class Solution(object):
    def gameOfLife(self, board):
        """
        :type board: List[List[int]]
        :rtype: void Do not return anything, modify board in-place instead.
        """
        if board and board[0]:
            M, N = len(board), len(board[0])
            board_next = copy.deepcopy(board)
            for m in range(M):
                for n in range(N):
                    lod = self.liveOrDead(board, m, n)
                    if lod == 2:
                        board_next[m][n] = 0
                    elif lod == 1:
                        board_next[m][n] = 1
            for m in range(M):
                for n in range(N):
                    board[m][n] = board_next[m][n]
            
    def liveOrDead(self, board, i, j):# return 0-nothing,1-live,2-dead
        ds = [(1, 1), (1, -1), (1, 0), (-1, 1), (-1, 0), (-1, -1), (0, 1), (0, -1)]
        live_count = 0
        M, N = len(board), len(board[0])
        for d in ds:
            r, c = i + d[0], j + d[1]
            if 0 <= r < M and 0 <= c < N:
                if board[r][c] == 1:
                    live_count += 1
        if live_count < 2 or live_count > 3:
            return 2
        elif board[i][j] == 1 or (live_count == 3 and board[i][j] ==0):
            return 1
        else:
            return 0
原文地址:https://www.cnblogs.com/LLLLgR/p/14787569.html