题目描述
根据 百度百科 ,生命游戏,简称为生命,是英国数学家约翰·何顿·康威在 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]
]
提示
-
你可以使用原地算法解决本题吗?请注意,面板上所有格子需要同时被更新:你不能先更新某些格子,然后使用它们的更新后的值再更新其他格子。
-
本题中,我们使用二维数组来表示面板。原则上,面板是无限的,但当活细胞侵占了面板边界时会造成问题。你将如何解决这些问题?
解题思路
1.卷积
❓那么什么是卷积?
用一个模板(kernel)和一幅图像进行卷积,对于图像上的一个点,让模板的原点和该点重合,然后模板上的点和图像上对应的点相乘,然后各点的积相加,就得到该点的卷积值。对图像上的每个点都这样处理。(注:这里的卷积是CV中常用的卷积和数字信号处理中的略有区别)
下面的图是一个没有补零(zero padding)的2维卷积的示意图,深蓝色的3x3的正方形就是所说的kernel,下面的浅蓝色就是图像,通过滑动kernel并且将kernel的对应位置和它覆盖区域的对应位置的数值相乘并加和,就可以得到卷积后某一个位置的值,大家查看这篇理解神经网络的卷积计算
下图展示的是带zero padding的2d卷积操作,也是为了方便处理我们的数据(本题中同样采用补零,如果不在原始的board的周围补零,对于board最外围的一圈值处理起来比较麻烦,而通过补零我们可以统一进行处理)
2.领域
方法很简单暴力,直接统计每个位置的 8 连通分量并根据部落数进行题目所说的判断就好了。
由于一次迭代是同时进行的,所以不能一边统计一边修改数组,这样会影响后面的判断。我的做法是先把输入的面板复制了一份,这样使用原始的 board 去判断部落数,更新放在了新的 board_next 上不会影响之前的 board。最后再把数值复制过来。
题目给了 4 个存活和死亡的判断条件,直接按照这4个条件判断即可。我定义了一个函数liveOrDead()用来判断当前判断的部落应该活还是死,返回结果的解释:0-不变, 1-活下来,2-要死了。
时间复杂度是O(MN),空间复杂度是O(MN).
代码实现
# 卷积方式实现
class Solution:
def gameOfLife(self, board: List[List[int]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
import numpy as np
r,c=len(board),len(board[0])
#下面两行做zero padding
board_exp=np.array([[0 for _ in range(c+2)] for _ in range(r+2)])
board_exp[1:1+r,1:1+c]=np.array(board)
#设置卷积核
kernel=np.array([[1,1,1],[1,0,1],[1,1,1]])
#开始卷积
for i in range(1,r+1):
for j in range(1,c+1):
#统计细胞周围8个位置的状态
temp_sum=np.sum(kernel*board_exp[i-1:i+2,j-1:j+2])
#按照题目规则进行判断
if board_exp[i,j]==1:
if temp_sum<2 or temp_sum>3:
board[i-1][j-1]=0
else:
if temp_sum==3:
board[i-1][j-1]=1
return board
# 领域方式实现
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