生命游戏的优雅解法:从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)来同时表示当前状态和未来状态,从而避免使用额外空间。

相关推荐
KaMeidebaby5 小时前
卡梅德生物技术快报|PD1 单克隆抗体定制配套 N 糖全谱质控开发
前端·人工智能·算法·数据挖掘·数据分析
8Qi86 小时前
LeetCode 235. 二叉搜索树的最近公共祖先(LCA)
算法·leetcode·二叉树·递归·二叉搜索树·lca·迭代
bIo7lyA8v6 小时前
算法稳定性分析中的随机扰动建模的技术8
算法
sugar__salt6 小时前
从栈队列数据结构到JS原型面向对象全解
前端·javascript·数据结构
科研online7 小时前
基于多源数据和XGBoost-SHAP分析中国大陆绿地碳汇空间变异影响因素的非线性相关性与尺度差异
算法·学习方法
Cthy_hy7 小时前
拓扑排序超详解:原理 + Kahn 贪心算法
python·算法·贪心算法
提子拌饭1337 小时前
逛三园游戏——基于鸿蒙PC Electron框架实现
前端·javascript·游戏·华为·electron·鸿蒙
三品吉他手会点灯8 小时前
C语言学习笔记 - 43.运算符与表达式 - 运算符1 - 运算符的分类和简单介绍
c语言·笔记·学习·算法
VkN2X2X4b8 小时前
算法复杂度的实验验证与误差分析的技术8
算法
其利天下技术8 小时前
风扇灯无刷电机自适应算法实战指南
算法·cocos2d·无刷电机自适应算法·bldc驱动自适应算法·其利无刷电机驱动算法