当前位置: 首页 > 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.dtcms.com/a/319184.html

相关文章:

  • 解决chrome下载crx文件被自动删除,加载未打包的扩展程序时提示“无法安装扩展程序,因为它使用了不受支持的清单版本解决方案”
  • 冷库温湿度物联网监控系统解决方案:冷链智能化
  • 普通冷库如何升级物联网冷库?工业智能网关赋能冷链智能化转型
  • AI摄像机如何为煤矿减少90%违规事故?
  • HarmonyOS 页面跳转新方案:HMRouter 路由框架全方位使用指南与实践案例
  • Axure 高阶设计:打造“以假乱真”的多图片上传组件
  • 如何使用vLLM运行gpt-oss
  • Nodejs》》MySql
  • 单链表专题---暴力算法美学(1)(有视频演示)
  • Keil MDK-ARM V5.42a 完整安装教程
  • 如何使用Ollama在本地运行gpt-oss
  • 09-netty基础-手写rpc-原理-01
  • 上位机知识篇---aptapt-get
  • 全栈:怎么把sql导入SQLserver里面
  • [特殊字符] 2025年生成式大模型部署与推理优化全景解析
  • STM32 串口控制电机运行系统
  • PyTorch + PaddlePaddle 语音识别
  • 【基础】go进阶学习笔记
  • Android渲染/合成底层原理详解
  • B 站 SEO 优化全景指南:从基础到进阶的实操方法
  • 贪心+矩阵算法
  • Oracle 关闭 impdp任务
  • 云原生安全挑战与治理策略:从架构思维到落地实践
  • 基于大数据的美食视频播放数据可视化系统 Python+Django+Vue.js
  • 解读 gpt-oss-120b 和 gpt-oss-20b开源模型
  • 仓库管理系统-20-前端之记录管理的联表查询
  • Android中视图测量、布局、绘制过程
  • 嵌入式 - 数据结构:二叉树
  • GitHub 上 Star 数量前 20 的开源 AI 项目
  • X4000 私有 5G 实验室入门套件