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

文章目录

    • 摘要
    • 描述
    • 解决方案
    • 解析问题与解决方案
      • 关键细节逐条讲
    • 示例与运行结果
    • 时间复杂度
    • 空间复杂度
    • 总结

摘要

这题的目标是设计一个“贪吃蛇”核心引擎:给定棋盘大小和一串食物位置,支持不断调用 move(direction) 推进游戏,返回当前分数,撞墙或咬到自己就结束(返回 -1)。本文用 Swift 从零实现一个能跑起来的 Demo,包括完整的 SnakeGame 类、关键数据结构设计、边界与碰撞处理、以及几组样例跑数。代码贴近实战,既能交作业,也能当你写小型游戏/面试题的参考模板。

描述

题目要我们实现三个能力:

  • SnakeGame(width, height, food): 初始化棋盘和食物队列。

  • move(direction: String) -> Int: 让蛇向 U/D/L/R 走一步;

    • 吃到食物:长度 +1,分数 +1。
    • 普通移动:头前进一格,尾巴同时挪走一格。
    • 撞墙/咬到自己:返回 -1(游戏结束)。
  • 返回值:当前分数(吃到几次食物)。若结束,始终返回 -1。

要点在于:如何让每次 move 都保持 O(1) 或接近 O(1) 的时间复杂度。

解决方案

高效思路(也是业界常见写法):

  1. 双端结构存蛇身
    用一个可 O(1) 头插、尾删的结构维护蛇身顺序(头在尾端更直观),我们用一个简易的 双向链表

  2. 快速查重靠哈希集合
    Set 存蛇身占用的格子,一步就能判断“咬到自己”。

  3. 一维编码坐标
    位置 (r, c) 编码为 id = r * width + c。既省内存又快。

  4. 先判断是否吃到食物

    • 吃到:不移除尾巴(蛇变长)。
    • 没吃到:把尾巴从集合里移除(因为尾巴会移动),再判断新头是否与身体冲突。
  5. 边界/自撞检查

    • 越界直接结束。
    • 自撞:若新头位置已经在集合里(注意“没吃到食物”的情况下我们先拿掉尾巴再查重)。

解析问题与解决方案

下面是完整可运行的 Swift 代码(命令行/Playground 都能跑)。为了 O(1) 头插尾删,我们写了个轻量的双向链表 Deque;蛇身用 Deque<Int> 维护,哈希表记录占用。

import Foundation// 轻量双向链表(Deque)——支持 O(1) 头删尾插等操作
final class Deque<T> {final class Node<T> {var val: Tvar prev: Node<T>?var next: Node<T>?init(_ v: T) { self.val = v }}private var head: Node<T>?private var tail: Node<T>?private(set) var count: Int = 0var isEmpty: Bool { count == 0 }func pushBack(_ v: T) {let node = Node(v)if let t = tail {t.next = nodenode.prev = ttail = node} else {head = nodetail = node}count += 1}func popFront() -> T? {guard let h = head else { return nil }let v = h.vallet nh = h.nextnh?.prev = nilhead = nhif nh == nil { tail = nil }count -= 1return v}func back() -> T? { tail?.val }func front() -> T? { head?.val }
}// 核心:SnakeGame
final class SnakeGame {private let width: Intprivate let height: Intprivate let food: [[Int]]private var foodIndex: Int = 0// 蛇身:尾在 front,头在 backprivate var body = Deque<Int>()// 占用表:快速判断“撞到自己”private var occupied = Set<Int>()private var score = 0// 方向映射private let dirMap: [String: (Int, Int)] = ["U": (-1, 0), "D": (1, 0),"L": (0, -1), "R": (0, 1)]init(_ width: Int, _ height: Int, _ food: [[Int]]) {self.width = widthself.height = heightself.food = food// 初始蛇:长度为 1,在 (0,0)let startId = 0body.pushBack(startId)occupied.insert(startId)score = 0foodIndex = 0}// 坐标与一维 id 的互转private func idOf(_ r: Int, _ c: Int) -> Int { r * width + c }private func rcOf(_ id: Int) -> (Int, Int) { (id / width, id % width) }// 一步移动:返回当前分数,若失败返回 -1func move(_ direction: String) -> Int {guard let (dr, dc) = dirMap[direction] else { return -1 }guard let headId = body.back() else { return -1 }let (hr, hc) = rcOf(headId)let nr = hr + drlet nc = hc + dc// 1) 越界?if nr < 0 || nr >= height || nc < 0 || nc >= width {return -1}let newHead = idOf(nr, nc)// 2) 是否吃到食物?let willEat = (foodIndex < food.count &&food[foodIndex][0] == nr &&food[foodIndex][1] == nc)// 3) 如果不会吃到,尾巴要移动:先从占用表移除尾巴if !willEat {if let tailId = body.front() {_ = body.popFront()occupied.remove(tailId)}}// 4) 自撞?if occupied.contains(newHead) {return -1}// 5) 头前进:加入蛇身与占用表body.pushBack(newHead)occupied.insert(newHead)// 6) 吃到食物:分数+1,食物指针后移if willEat {score += 1foodIndex += 1}return score}
}

关键细节逐条讲

  • 为什么先“挪尾再查撞”?
    蛇不吃的时候,下一步尾巴会离开原位置。所以你可以“允许头移动到原尾巴位置”。实现上就表现为:把尾巴从 occupied 里移除,再查新头是否在 occupied。这能避免错判“咬到自己”。

  • 数据结构选型

    • Deque:我们只需要 O(1) 地在“尾部加头”、“头部去尾”,链表最直觉;自己实现很轻,避免 Array.removeFirst() 的 O(n)。
    • Set:哈希查重 O(1),非常关键。
    • 一维编码位置:让 Set<Int> 更轻量,少了元组 hash 的开销。
  • 食物逻辑

    • “吃到食物不移除尾巴”是增长的本质。
    • foodIndex 单调递增就行,省去额外数据结构。
  • 方向映射
    简单 Dictionary,易于拓展(比如以后加入斜向就是改这儿)。

示例与运行结果

下面给一段可运行的 Demo,包含两组测试:一组是经典样例,一组是我自定义的压力测试(快速吃两个食物、触发自撞)。

// MARK: - Demo 1(LeetCode 常见用例)
do {let game = SnakeGame(3, 2, [[1,2],[0,1]])print("Demo 1")print(game.move("R")) // 0print(game.move("D")) // 0print(game.move("R")) // 1 (吃到 [1,2])print(game.move("U")) // 1print(game.move("L")) // 2 (吃到 [0,1])print(game.move("U")) // -1 (撞墙)print("----")
}// MARK: - Demo 2(连续吃食物 + 自撞)
do {let game = SnakeGame(4, 3, [[0,1],[0,2],[0,3]])print("Demo 2")print(game.move("R")) // 1print(game.move("R")) // 2print(game.move("R")) // 3print(game.move("D")) // 3print(game.move("L")) // 3print(game.move("U")) // -1(向上回到自己身体)print("----")
}

可能输出(你的控制台应该是一致的):

Demo 1
0
0
1
1
2
-1
----
Demo 2
1
2
3
3
3
-1
----

你可以在 Playground 或命令行直接复制粘贴运行,感受一下每一步的状态变化。

时间复杂度

  • 每次 move
    全部是 O(1) 操作:

    • 链表尾插/头删 O(1)
    • Set 增删查 O(1)
    • 一次边界和食物判断 O(1)
      因此整题可以轻松达到 均摊 O(1)
  • 初始化
    O(1)(食物数组只是引用,未做预处理)。

空间复杂度

  • 蛇身链表:最多占用 O(k)(k 为当前蛇长)。
  • 占用集合:同样 O(k)。
  • 食物数组:O(f)(f 为食物个数)。
  • 其他都是常数。

整体空间:O(k + f)

总结

这道题表面是小游戏,实则是数据结构设计题。抓住三件事就能写得又快又稳:

  1. Deque + Set 保证 O(1) 移动与查重;
  2. “不吃时先挪尾巴再查撞”这一步是避免误判的关键;
  3. 一维编码位置提升哈希效率,让实现更简洁。

如果你以后要把它扩展成单机小游戏,渲染层(SwiftUI、SpriteKit)只需要消费 move 的结果,按照 Deque 里的坐标把节点画出来即可;引擎层完全复用本文代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/95467.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/95467.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/95467.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025-08-15:按对角线进行矩阵排序。用go语言,给你一个 n × n 的整数矩阵,要求返回一个按下面规则调整后的矩阵: - 将每一条与主对角线平行的斜线视为一个序列。对于位于主对角线及其下方的

2025-08-15&#xff1a;按对角线进行矩阵排序。用go语言&#xff0c;给你一个 n n 的整数矩阵&#xff0c;要求返回一个按下面规则调整后的矩阵&#xff1a;将每一条与主对角线平行的斜线视为一个序列。对于位于主对角线及其下方的那些斜线&#xff08;即所在位置的行索引 ≥ …

MySQL相关概念和易错知识点(5)(索引、事务、MVCC)

目录1.索引&#xff08;1&#xff09;局部性原理a.局部性原理在计算机中的地位b.pagec.池化技术&#xff08;Buffer Pool&#xff09;&#xff08;2&#xff09;如何理解索引&#xff08;3&#xff09;索引的原理a.page的构成b.多层目录c.基于B树的索引①B树的特性在索引中的作…

SQLite 子查询

SQLite 子查询 SQLite 是一个轻量级的数据库管理系统&#xff0c;广泛应用于移动设备、嵌入式系统和桌面应用。在处理复杂的查询时&#xff0c;子查询&#xff08;Subquery&#xff09;是SQLite数据库查询语言中的一个强大工具。本文将详细介绍SQLite子查询的概念、用法及其在数…

区块链系统审计方法论:全面指南与Python实践

目录 区块链系统审计方法论:全面指南与Python实践 1. 引言 2. 区块链审计框架 3. 智能合约审计关键技术 3.1 静态代码分析 3.2 符号执行(Symbolic Execution) 4. 共识机制审计 4.1 PoW共识验证 4.2 PBFT共识模拟 5. 数据完整性审计 5.1 Merkle树验证 6. 完整审计系统实现 7.…

分布式锁—Redisson的公平锁

1.Redisson公平锁RedissonFairLock概述 (1)非公平和公平的可重入锁 一.非公平可重入锁 锁被释放后&#xff0c;排队获取锁的线程会重新无序获取锁&#xff0c;没有任何顺序性可言。 二.公平可重入锁 锁被释放后&#xff0c;排队获取锁的线程会按照请求获取锁时候的顺序去获取…

上网行为安全概述和组网方案

一、上网行为安全概述1. 背景与需求互联网的双刃剑特性&#xff1a;网络普及改变工作生活方式&#xff0c;业务向互联网迁移。缺乏管理导致风险&#xff1a;带宽滥用、监管困难、信息泄露、网络违法、安全威胁。核心问题&#xff1a;带宽滥用&#xff1a;P2P/流媒体占用70%带宽…

某处卖600的【独角仙】尾盘十分钟短线 尾盘短线思路 手机电脑通用无未来函数

通达信指标【独角仙】尾盘十分钟套装-主图-副图-选古指标&#xff0c;支持手机电脑使用。在股市收盘的前十分钟第二天冲高卖出&#xff0c;信号可以盘中预警也可以尾盘选股&#xff0c;如果要保证信号固定建议是尾盘选股即可&#xff0c;当天信号固定后&#xff0c;不会产生漂移…

日志数据链路的 “搬运工”:Flume 分布式采集的组件分工与原理

flume详解&#xff1a;分布式日志采集的核心原理与组件解析 在大数据体系中&#xff0c;日志采集是数据处理的第一步。Flume 作为 Apache 旗下的分布式日志采集工具&#xff0c;以高可用、高可靠、易扩展的特性&#xff0c;成为处理海量日志数据的首选方案。本文将从 Flume 的…

大消费新坐标中的淘宝大会员

一站式消费需要一站式权益。作者|古廿编辑|杨舟淘宝的大会员体系落地了。8月6日&#xff0c;淘宝首次整合饿了么、飞猪等阿里系平台资源&#xff0c;推出覆盖购物、外卖、出行、旅游的一体化会员体系——用户在三大平台的消费&#xff0c;都能累积淘气值&#xff0c;根据淘气值…

MIME(多用途互联网邮件扩展)

MIME&#xff08;Multipurpose Internet Mail Extensions&#xff09; MIME 是 多用途互联网邮件扩展 的缩写&#xff0c;它最初是为了解决传统电子邮件只能传输纯文本的局限性而设计的&#xff0c;后来逐渐成为互联网中 数据格式标识与传输 的通用标准&#xff0c;被广泛应用…

PHP imagick扩展安装以及应用

Date: 2025-08-13 10:48:12 author: lijianzhan php_imagick是PHP的一个强大的扩展模块&#xff0c;用于调用ImageMagick图像处理库的功能&#xff0c;支持处理JPEG、PNG、GIF等超过185种格式的图像&#xff0c;实现缩放、旋转、动画生成等操作&#xff0c;常用于网页图片动态生…

2025年度14款CRM销售管理系统横向评测

本文深入对比了以下14款CRM销售管理软件&#xff1a;1.纷享销客&#xff1b; 2.Zoho CRM&#xff1b; 3.红圈销售&#xff1b; 4.销帮帮&#xff1b; 5.Salesforce&#xff1b; 6.Pipedrive&#xff1b; 7.Microsoft Dynamics 365&#xff1b; 8.悟空 CRM&#xff1b; 9.励销云…

akamai鼠标轨迹

各位肯定被akamai鼠标轨迹、点击事件、键盘事件&#xff0c;网页交互困扰 那么我们就研究一下鼠标轨迹、点击事件AST解混淆, 拿到解混淆后的代码&#xff0c; 如下&#xff0c;sensor_data就是我们要搞的参数 如何解混淆这里就不赘述了&#xff0c;需要的可以看我上一篇文章&am…

飞算JavaAI开发全流程解析:从自然语言到可运行工程的智能进化

引言 在数字经济时代&#xff0c;企业级应用开发面临着需求多变、交付周期紧、质量要求高的三重挑战。传统Java开发模式依赖人工进行需求确认、架构设计、代码编写和测试验证&#xff0c;导致开发效率低下、沟通成本高企。据统计&#xff0c;一个中等规模的项目需要平均8周完成…

垃圾回收标记算法:三色标记

文章目录1 三色标记流程1.1 初始标记1.2 并发标记1.3 重新标记1.4 清除阶段&#xff08;Sweep&#xff09;1.5 为什么初始标记和重新标记需要STW&#xff0c;而并发标记不需要?2 并发标记的写屏障3 多标问题4.漏标问题4.1 漏标的两个必要条件4.2 解决方案一&#xff1a;增量更…

反射的详解

目录一、反射1.JDK,JRE,JVM的关系2.什么是反射3. 三种获取Class对象(类的字节码)的方式4.Class常用方法5. 获取类的构造器6.反射获取成员变量&使用7.反射获取成员方法8.综合例子一、反射 1.JDK,JRE,JVM的关系 三者是Java运行环境的核心组成部分&#xff0c;从包含关系上看…

Grafana Tempo日志跟踪平台

以下是Grafana Tempo文档的总结&#xff08;基于最新版文档内容&#xff09;&#xff1a; 核心概念 分布式追踪系统&#xff1a;Tempo是开源的分布式追踪后端&#xff0c;专注于高吞吐量、低成本存储和与现有监控生态的深度集成 架构组成&#xff1a; Distributor&#xff1a…

Qt基本控件

Qt 的基本控件是构建用户界面的基础&#xff0c;涵盖了按钮、输入框、容器、显示组件等&#xff0c;适用于传统 Widget 开发&#xff08;基于 QWidget&#xff09;。以下是常用基本控件的分类总结&#xff1a;一、按钮类控件用于触发交互操作&#xff0c;如提交、取消、选择等。…

用Voe3做AI流量视频,条条10W+(附提示词+白嫖方法)

最近 AI 视频的风从大洋彼岸吹过来&#xff0c;Voe3 的技术升级&#xff0c;诞生了很多很有意思的玩法。 比如&#xff1a;AI ASMR 切水果解压视频&#xff0c;卡皮巴拉旅行博主、雪怪 AI Vlog&#xff0c;动物奥运会、第一人称视角穿越古战场直播。 这些视频的流量很好&…

嵌入式学习的第四十八天-中断+OCP原则

一、GIC通用中断控制器 1.GIC通用中断控制器 GIC 是 ARM 公司给 Cortex-A/R 内核提供的一个中断控制器&#xff0c;GIC接收众多外部中断&#xff0c;然后对其进行处理&#xff0c;最终通过VFIQ、VIRQ、FIQ 和 IRQ给内核&#xff1b;这四个 信号的含义如下&#xff1a; VFIQ:虚拟…