生命游戏的优雅解法:从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) 空间


朴素解法:使用额外矩阵

思路

创建一个大小为 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)来同时表示当前状态和未来状态,从而避免使用额外空间。

相关推荐
2301_800895101 小时前
蓝桥杯第十二届b组国赛真题--备战国赛版h
算法·蓝桥杯
手写码匠1 小时前
手写 AI Prompt Injection 防护系统:从零实现 LLM 安全边界
人工智能·深度学习·算法·aigc
薇茗1 小时前
【初阶数据结构】 升沉有序的平仄 排序
c语言·数据结构·算法·排序算法
_深海凉_1 小时前
LeetCode热题100-对称二叉树
算法·leetcode·职场和发展
瑞华丽PLM1 小时前
瑞华丽工业软件研发效能全景展示
人工智能·算法·cae·工业软件·国产软件·瑞华丽plm·瑞华丽
运筹vivo@2 小时前
两数之和(leetcode)
算法·leetcode·职场和发展
吃好睡好便好2 小时前
在Matlab中绘制二维等高线图
开发语言·人工智能·学习·算法·matlab
Mr_pyx2 小时前
LeetCode Hot 100 - 最长递增子序列完全题解
算法·leetcode·职场和发展
Mr_pyx2 小时前
LeetCode Hot 100 - 爬楼梯完全题解
算法·动态规划