生命游戏的优雅解法:从O(mn)空间到O(1)空间的进阶之旅

问题描述

给定一个 m × n 的二进制矩阵,每个单元格代表一个细胞在某一代的状态:

  • 1 → 活细胞
  • 0 → 死细胞

根据以下规则计算下一代:

  1. 任何活细胞,如果活邻居少于2个,则死亡(人口不足)
  2. 任何活细胞,如果有2或3个活邻居,则存活到下一代
  3. 任何活细胞,如果有超过3个活邻居,则死亡(人口过剩)
  4. 任何死细胞,如果恰好有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) 空间
  • 优化解法 不使用额外矩阵 - O(m×n) 时间和 O(1) 空间

朴素解法:使用额外矩阵

思路

创建一个大小为 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表示当前是活细胞)。

算法步骤

  1. 第一次遍历:对每个单元格统计活邻居数量,并根据规则将细胞标记为2或3
  2. 第二次遍历:将标记值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)来同时表示当前状态和未来状态,从而避免使用额外空间。

相关推荐
UIU1144 小时前
P1233 [ICPC 2001 Taejon R] 木棍加工
数据结构·c++·算法·动态规划
论文复现现场5 小时前
WRF、FEA 能用 L40S 加速吗?求解器检查、断点恢复与长期运行
java·数据库·算法·wrf·有限元分析·gpu计算·断点续算
ZhengEnCi5 小时前
用 AI 写代码为什么拼到最后还是拼人:算力瓶颈与领域专家提示词的本质差异
算法·架构
秋天的怀念_David6 小时前
第二十一夜 · 数字黑洞与西西弗斯的惩罚
java·程序人生·算法
碧海银沙音频科技研究院7 小时前
在PC电脑上用Whisper→Marian算法实现离线语音翻译功能
算法·whisper
niyongsheng7 小时前
3D 装箱算法工程化:数据结构、放置启发式与支撑规则
数据结构·算法·rust
叉烧滑蛋9 小时前
B站台湾大学郭彦甫|MATLAB 学习笔记|04 变量与档案存取
数据结构
j7~10 小时前
【穷举、深搜与回溯算法】一棵决策树讲透全排列与子集
数据结构·c++·算法·leetcode·回溯·递归
驱动小百科10 小时前
国产3A游戏《影之刃零》正式发售:售价及电脑配置要求介绍
游戏·影之刃零·影之刃零配置要求·影之刃零下载·影之刃零售价
傅科摆 _ py11 小时前
DP,贪心,堆和栈,二分力扣复习
开发语言·python·算法