问题描述
给定一个 m × n 的二进制矩阵,每个单元格代表一个细胞在某一代的状态:
- 1 → 活细胞
- 0 → 死细胞
根据以下规则计算下一代:
- 任何活细胞,如果活邻居少于2个,则死亡(人口不足)
- 任何活细胞,如果有2或3个活邻居,则存活到下一代
- 任何活细胞,如果有超过3个活邻居,则死亡(人口过剩)
- 任何死细胞,如果恰好有3个活邻居,则变成活细胞(繁殖)
注意: 邻居是指水平、垂直或对角线方向上的相邻单元格。
示例
示例1:
输入:
[0, 0, 0, 0]
[0, 0, 1, 0]
[0, 1, 1, 0]
[0, 0, 0, 0]
输出:
[0, 0, 0, 0]
[0, 1, 1, 0]
[0, 1, 1, 0]
[0, 0, 0, 0]
示例2:
输入:
[0, 0, 0, 0, 0]
[0, 1, 1, 0, 0]
[1, 1, 0, 0, 0]
[0, 0, 0, 1, 0]
[0, 0, 1, 0, 0]
输出:
[0, 0, 0, 0, 0]
[1, 1, 1, 0, 0]
[1, 1, 0, 0, 0]
[0, 1, 1, 0, 0]
[0, 0, 0, 0, 0]
目录
-
朴素解法\] 使用额外矩阵 - O(m×n) 时间和 O(m×n) 空间
朴素解法:使用额外矩阵
思路
创建一个大小为 m × n 的辅助矩阵 nextGen[][] 来存储下一代的细胞状态。遍历原始矩阵 mat[][],对每个单元格统计其活邻居数量(值为1的邻居)。
- 如果当前细胞是死的(值为0)且活邻居数量等于3,则
nextGen[x][y] = 1 - 如果当前细胞是活的(值为1)且活邻居数量不等于2或3,则
nextGen[x][y] = 0 - 其他情况保持原值
代码实现
python
def findNextGen(mat):
m, n = len(mat), len(mat[0])
nextGen = [[0]*n for _ in range(m)]
# 八个方向的邻居偏移
directions = [
(0,1), (1,0), (0,-1), (-1,0),
(1,1), (-1,-1), (1,-1), (-1,1)
]
for i in range(m):
for j in range(n):
live = 0
for dx, dy in directions:
x, y = i + dx, j + dy
if 0 <= x < m and 0 <= y < n and mat[x][y] == 1:
live += 1
# 应用规则
if mat[i][j] == 1 and (live < 2 or live > 3):
nextGen[i][j] = 0
elif mat[i][j] == 0 and live == 3:
nextGen[i][j] = 1
else:
nextGen[i][j] = mat[i][j]
return nextGen
最近在啃《生命游戏》的空间优化解法,从O(mn)到O(1)确实烧脑。推荐一个宝藏网站图码,专门帮人搞定这类算法难题。它把60多种数据结构和算法做成交互式动画,你输入自定义数据就能自动生成可视化过程,甚至支持上传C/C++/Java/Python代码直接解析。无论是备战408考研,还是突击数据结构期末考试,都能边看动画边理解,7*24小时AI还能随时解释代码。快去试试,保证比干看文字效率高。
图码-数据结构与算法交互式可视化平台
访问网站:https://totuma.cn
复杂度分析
- 时间复杂度: O(m × n × 8) = O(m × n),每个单元格检查8个邻居
- 空间复杂度: O(m × n),需要额外矩阵存储结果
优化解法:不使用额外矩阵
核心思想
我们可以在原矩阵上直接修改,通过使用额外的标记值来区分当前状态和未来状态:
- 2:表示当前是死细胞,但下一代将变成活细胞
- 3:表示当前是活细胞,但下一代将变成死细胞
这样,在统计邻居数量时,我们仍然可以正确判断当前细胞的原始状态(值为1或3表示当前是活细胞)。
算法步骤
- 第一次遍历:对每个单元格统计活邻居数量,并根据规则将细胞标记为2或3
- 第二次遍历:将标记值2转换为1,标记值3转换为0,其他保持不变
代码实现
python
def findNextGen(mat):
m, n = len(mat), len(mat[0])
directions = [(0,1), (1,0), (0,-1), (-1,0),
(1,1), (-1,-1), (1,-1), (-1,1)]
# 第一次遍历:标记状态
for i in range(m):
for j in range(n):
live = 0
for dx, dy in directions:
x, y = i + dx, j + dy
if 0 <= x < m and 0 <= y < n and (mat[x][y] == 1 or mat[x][y] == 3):
live += 1
if mat[i][j] == 1 and (live < 2 or live > 3):
mat[i][j] = 3 # 活→死
elif mat[i][j] == 0 and live == 3:
mat[i][j] = 2 # 死→活
# 第二次遍历:还原标记值
for i in range(m):
for j in range(n):
if mat[i][j] == 2:
mat[i][j] = 1
elif mat[i][j] == 3:
mat[i][j] = 0
return mat
复杂度分析
- 时间复杂度: O(m × n),两次遍历,每次检查8个邻居
- 空间复杂度: O(1),原地修改,只使用常数额外空间
总结
| 方法 | 时间复杂度 | 空间复杂度 | 特点 |
|---|---|---|---|
| 朴素解法 | O(m×n) | O(m×n) | 思路简单,容易理解 |
| 优化解法 | O(m×n) | O(1) | 空间效率高,适合大矩阵 |
两种方法的时间复杂度相同,但优化解法通过巧妙的标记技术,将空间复杂度降低到了常数级别。在实际应用中,如果矩阵规模很大,优化解法会更有优势。
关键技巧: 使用额外的状态值(2和3)来同时表示当前状态和未来状态,从而避免使用额外空间。