当前位置: 首页 > news >正文

Swift 实战:高效设计 Tic-Tac-Toe 游戏逻辑(LeetCode 348)

在这里插入图片描述
在这里插入图片描述

文章目录

    • 摘要
    • 描述
      • 题目需求:
      • 示例:
    • 题解答案
    • 题解代码分析
      • Swift 解法代码:
      • 代码详细解释:
        • 1. 初始化:
        • 2. 落子记录:
        • 3. 判断胜负:
    • 示例测试及结果
      • 输出结果:
    • 时间复杂度
    • 空间复杂度
    • 总结

摘要

如果让你实现一个两人对战的井字棋(Tic-Tac-Toe)游戏逻辑,你会怎么做?是不是会直接用二维数组存每个格子的状态,然后在每次落子后从头到尾检查是否赢了?

这种方式虽然直观,但效率并不高,特别是当棋盘扩大到 n×n 的时候。LeetCode 348 这个题目就挑战我们去优化这个过程:每次落子之后都要快速判断胜负,尽可能降低计算开销。

今天我们用 Swift 来实现这个游戏逻辑,看看怎么用一套简洁又高效的数据结构,把这个小游戏做得更聪明些。

描述

这个问题让我们自己“设计”一个井字棋游戏类。也就是说,不是解一个单独的逻辑判断题,而是写出能不断调用和处理的游戏控制结构。

题目需求:

设计一个井字棋游戏类 TicTacToe

init(_ n: Int)
  • 初始化一个 n x n 的棋盘。
func move(_ row: Int, _ col: Int, _ player: Int) -> Int
  • 玩家 player(row, col) 落子。

  • 玩家编号为 1 或 2。

  • 返回值为:

    • 0:没有胜者;
    • 1:玩家 1 获胜;
    • 2:玩家 2 获胜。

示例:

TicTacToe toe = TicTacToe(3)
toe.move(0, 0, 1) // 返回 0(无胜者)
toe.move(0, 2, 2) // 返回 0
toe.move(2, 2, 1) // 返回 0
toe.move(1, 1, 2) // 返回 0
toe.move(2, 0, 1) // 返回 0
toe.move(1, 0, 2) // 返回 0
toe.move(2, 1, 1) // 返回 1(玩家 1 胜出)

题解答案

这道题如果用传统方式去解决,比如在 move() 每次落子后遍历整行、整列、对角线判断是否有玩家胜出,那每次 move 的时间复杂度就是 O(n)。

但我们可以更优化地去思考:我们其实不需要知道整个棋盘状态,只需要知道某一行、某一列或对角线的总和是否等于 n 就可以判断胜负。

于是我们换个角度:

  • 为每行、每列维护一个计数数组;
  • 为两个对角线也维护两个值;
  • 玩家 1 每次落子时计为 +1
  • 玩家 2 落子时计为 -1
  • 当某行、列或对角线的总和变为 +n-n 时,就表示某一方胜出。

这样做的核心优势在于:每次落子都是 O(1) 时间复杂度。

题解代码分析

Swift 解法代码:

class TicTacToe {private var rows: [Int]private var cols: [Int]private var diagonal: Intprivate var antiDiagonal: Intprivate let n: Intinit(_ n: Int) {self.n = nself.rows = Array(repeating: 0, count: n)self.cols = Array(repeating: 0, count: n)self.diagonal = 0self.antiDiagonal = 0}func move(_ row: Int, _ col: Int, _ player: Int) -> Int {// 玩家 1 记作 +1,玩家 2 记作 -1let toAdd = (player == 1) ? 1 : -1// 更新对应行列rows[row] += toAddcols[col] += toAdd// 判断对角线if row == col {diagonal += toAdd}// 判断反对角线if row + col == n - 1 {antiDiagonal += toAdd}// 胜负判断if abs(rows[row]) == n || abs(cols[col]) == n || abs(diagonal) == n || abs(antiDiagonal) == n {return player}// 没人赢return 0}
}

代码详细解释:

1. 初始化:
self.rows = Array(repeating: 0, count: n)
  • 用一个数组来记录每一行的累积值,初始为 0。
2. 落子记录:
let toAdd = (player == 1) ? 1 : -1
  • 用不同正负值来区别玩家,从而避免需要分别用两个数组。
3. 判断胜负:
if abs(rows[row]) == n
  • 一行里如果都是 +1(或都是 -1),其绝对值就是 n,说明这个玩家在该行获胜。

这种判断方式不仅逻辑清晰,而且效率极高。

示例测试及结果

我们来跑一组测试用例:

let toe = TicTacToe(3)
print(toe.move(0, 0, 1)) // 输出 0
print(toe.move(0, 2, 2)) // 输出 0
print(toe.move(2, 2, 1)) // 输出 0
print(toe.move(1, 1, 2)) // 输出 0
print(toe.move(2, 0, 1)) // 输出 0
print(toe.move(1, 0, 2)) // 输出 0
print(toe.move(2, 1, 1)) // 输出 1,玩家 1 获胜!

输出结果:

0
0
0
0
0
0
1

可以看到,落子之后能快速返回胜负结果,且没有任何冗余遍历。

时间复杂度

每次调用 move()

  • 更新对应行列、对角线,只涉及常数次操作。
  • 所以时间复杂度是 O(1) —— 这是这道题的优化关键。

相比之下,传统方法每次都要 O(n) 遍历行列,性能差很多。

空间复杂度

  • 需要用两个数组记录每一行和列:O(n)
  • 还需两个整数变量记录对角线:O(1)
  • 所以空间复杂度是 O(n)

非常节省空间,且没有使用二维数组模拟棋盘(那样是 O(n²))。

总结

LeetCode 348 虽然看起来是一个游戏类设计题,但它背后真正考验的是:

  • 你能不能通过数据结构建模问题状态;
  • 能否在落子后常数时间内判断胜负
  • 在 n 比较大时是否还能稳定运行。

通过维护“计数数组”和两个对角线值,我们把传统 O(n) 的遍历过程优化成了 O(1) 的更新和判断。这个思路在很多游戏逻辑、模拟类题目中都非常有价值。

如果你将来要实现一个支持在线对战的 Tic-Tac-Toe 游戏,这套高效结构完全可以用在后台逻辑中,极大提升响应速度。

http://www.lryc.cn/news/612833.html

相关文章:

  • ansible-playbook之yum
  • Daemon Tools for Mac —— 专业虚拟光驱与磁盘映像工具
  • LeetCode 面试经典 150_数组/字符串_除自身以外数组的乘积(13_238_C++_中等)(前缀积)
  • 数据结构初阶(5)队列
  • java - 深拷贝 浅拷贝
  • KT148A 语音芯片自研板烧录方案:接口预留与电路连接指南
  • 线上业务突然流量掉 0 ?一次 DNS 污染排查与自救实录
  • itextPdf获取pdf文件宽高不准确
  • 无人机航拍数据集|第8期 无人机海上目标检测YOLO数据集3641张yolov11/yolov8/yolov5可训练
  • BES2700量产项目
  • 7. 什么是事件委托
  • 经营帮:重构企业经营全流程,打造产业互联网新生态
  • 上位机知识篇---AT指令
  • LabVIEW实验室测试框架
  • 复合机器人破局之路:如何逆袭突围
  • 强化学习详解:从理论到前沿的全面解析
  • 知识随记-----Qt 实用技巧:自定义倒计时按钮防止用户频繁点击
  • GitHub 趋势日报 (2025年08月06日)
  • 网络安全与软件定义汽车的发展
  • 【LLM】扩散模型与自回归模型:文本生成的未来对决
  • 分布式事务与分布式锁
  • “物联网+职业本科”:VR虚拟仿真实训室的发展前景
  • USB枚举介绍 以及linux USBFFS应用demo
  • 抖音、快手、视频号等多平台视频解析下载 + 磁力嗅探下载、视频加工(提取音频 / 压缩等)
  • Go语言Ebiten坦克大战
  • JVM类加载
  • Redis中间件(三):Redis存储原理与数据模型
  • Spring MVC拦截器与过滤器的区别详解
  • Ubuntu24.04的“errors from xkbcomp are not fatal to the X server”终极修复方案
  • Ethereum:如何优雅部署 NPM 包中的第三方智能合约?