跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Swift算法

LeetCode 390 消除游戏 Swift 算法解析

本题要求模拟从 1 到 n 的整数列表交替左右消除过程,直至剩一个数。由于 n 可达 10^9,直接模拟效率低。核心思路是利用数学规律:每轮消除后剩余数字构成等差数列。通过维护头元素 head、步长 step 和数量 count,可在 O(log n) 时间内推算结果。代码使用 Swift 实现,逻辑简洁且空间复杂度为 O(1)。适用于约瑟夫问题变种及大规模数据分批处理场景。

黑客发布于 2026/3/27更新于 2026/7/1150 浏览
LeetCode 390 消除游戏 Swift 算法解析

在这里插入图片描述

在这里插入图片描述

文章目录
  • 摘要
  • 描述
  • 题解答案
  • 题解代码分析
    • 核心观察:等差数列
    • 从左到右消除时 head 的更新
    • 从右到左消除时 head 的更新
    • step 和 count 的更新
    • 边界情况
    • 完整执行流程示例(n=9)
  • 示例测试及结果
    • 示例 1:n = 9
    • 示例 2:n = 1
    • 示例 3:n = 6
    • 示例 4:n = 2
  • 时间复杂度
  • 空间复杂度
  • 实际应用场景
    • 场景一:约瑟夫问题
    • 场景二:分批处理
    • 场景三:游戏逻辑
  • 总结

摘要

这道题其实挺有意思的,它要求我们模拟一个交替从左到右、从右到左的消除过程,最后找出剩下唯一一个数字。听起来像是约瑟夫问题的变种,但实际上可以通过数学规律来高效解决。

由于 n 最大可以到 10^9,如果直接模拟整个消除过程,时间和空间都会爆炸。我们需要找出其中的规律:每一轮消除后,剩余数字形成一个等差数列,我们只需要维护"头元素"和"步长",就能推算出下一轮的状态,而不需要真正维护整个数组。

文章配图

描述

题目要求是这样的:列表 arr 由在范围 [1, n] 中的所有整数组成,并按严格递增排序。请你对 arr 应用下述算法:

  1. 从左到右,删除第一个数字,然后每隔一个数字删除一个,直到到达列表末尾
  2. 重复上面的步骤,但这次是从右到左,删除最右侧的数字,然后剩下的数字每隔一个删除一个
  • 不断重复这两步,从左到右和从右到左交替进行,直到只剩下一个数字
  • 给你整数 n,返回 arr 最后剩下的数字。

    示例 1:

    输入: n = 9 输出: 6 解释: arr = [1, 2, 3, 4, 5, 6, 7, 8, 9] arr = [2, 4, 6, 8] arr = [2, 6] arr = [6] 
    

    示例 2:

    输入: n = 1 输出: 1 
    

    提示:

    • 1 <= n <= 10^9

    这道题的核心思路是:每一轮消除后,剩余数字构成一个等差数列。我们只需维护当前等差数列的"头元素"和"步长",以及剩余数量,就能用 O(log n) 的时间推算出最终答案,而不需要真正模拟整个数组。

    文章配图

    题解答案

    下面是完整的 Swift 解决方案:

    class Solution {
        func lastRemaining(_ n: Int) -> Int {
            if n == 1 {
                return 1
            }
            // head: 当前剩余序列的第一个元素
            var head = 1
            // step: 相邻剩余元素之间的差值(等差数列的公差)
            var step = 1
            // count: 剩余元素的数量
            var count = n
            // leftToRight: 本轮是否从左到右消除
            var leftToRight = true
            while count > 1 {
                if leftToRight {
                    // 从左到右:总是会删除第一个元素,所以 head 要后移
                    head += step
                } else {
                    // 从右到左:只有当剩余数量为奇数时,才会删到第一个元素
                    if count % 2 == 1 {
                        head += step
                    }
                }
                step *= 2
                count /= 2
                leftToRight.toggle()
            }
            return head
        }
    }
    

    题解代码分析

    让我们一步步分析这个解决方案。

    核心观察:等差数列

    每一轮消除后,剩余数字都形成一个等差数列。例如 n=9 时:

    • 初始:[1, 2, 3, 4, 5, 6, 7, 8, 9],头=1,步长=1,数量=9
    • 从左到右后:[2, 4, 6, 8],头=2,步长=2,数量=4
    • 从右到左后:[2, 6],头=2,步长=4,数量=2
    • 从左到右后:[6],头=6,步长=8,数量=1

    我们只需要维护 head(头元素)、step(步长)、count(剩余数量),就能完整描述当前状态,无需保存整个数组。

    从左到右消除时 head 的更新

    从左到右消除时,我们总是先删掉第一个元素,然后每隔一个删一个。因此,无论剩余数量是奇数还是偶数,原来的第一个元素都会被删掉,新的第一个元素就是原来的第二个元素。

    由于剩余序列是等差数列,相邻元素相差 step,所以新的 head 为 head + step:

    if leftToRight { head += step }
    
    从右到左消除时 head 的更新

    从右到左消除时,我们先删最右边,再每隔一个删。这时头元素是否被删,取决于剩余数量的奇偶性。

    • 若 count 为偶数:例如 [2, 4, 6, 8],从右删 8、4,剩下 [2, 6],头 2 保留,head 不变
    • 若 count 为奇数:例如 [2, 4, 6],从右删 6、2,剩下 [4],头 2 被删,新的头是 4,head 需要加上 step

    所以:

    if !leftToRight {
        if count % 2 == 1 {
            head += step
        }
    }
    
    step 和 count 的更新

    每轮消除后,相邻剩余元素之间的间隔会翻倍,因此 step *= 2。剩余数量大约减半,所以 count /= 2:

    step *= 2
    count /= 2
    leftToRight.toggle()
    
    边界情况

    当 n == 1 时,直接返回 1,无需进入循环。

    完整执行流程示例(n=9)
    1. head=1, step=1, count=9, leftToRight=true
      从左到右:head=2, step=2, count=4, leftToRight=false
    2. head=2, step=2, count=4, leftToRight=false
      count 为偶数,head 不变:head=2, step=4, count=2, leftToRight=true
    3. head=2, step=4, count=2, leftToRight=true
      从左到右:head=6, step=8, count=1, leftToRight=false
    4. count=1,退出循环,返回 6

    示例测试及结果

    示例 1:n = 9

    执行过程:

    1. 初始:head=1, step=1, count=9
    2. 从左到右:head=2, step=2, count=4
    3. 从右到左(count 偶数):head=2, step=4, count=2
    4. 从左到右:head=6, step=8, count=1
    5. 返回 6

    结果: 6

    示例 2:n = 1

    执行过程:

    • 直接返回 1,不进入循环

    结果: 1

    示例 3:n = 6

    模拟过程:

    • 初始:[1,2,3,4,5,6]
    • 从左到右:[2,4,6]
    • 从右到左:[4]
    • 返回 4

    算法过程:

    1. head=1, step=1, count=6,从左到右:head=2, step=2, count=3
    2. count 为奇数,从右到左会删到头:head=4, step=4, count=1
    3. 返回 4
    示例 4:n = 2

    模拟过程:

    • 初始:[1,2]
    • 从左到右:删除 1,剩下 [2]
    • 返回 2

    算法过程:

    1. head=1, step=1, count=2,从左到右:head=2, step=2, count=1
    2. 返回 2

    时间复杂度

    时间复杂度:O(log n)

    每一轮 count 约减半,因此循环次数约为 log₂(n)。对于 n = 10^9,大约 30 次循环即可结束。

    空间复杂度

    空间复杂度:O(1)

    只使用了 head、step、count、leftToRight 等少量变量,与 n 无关。

    实际应用场景

    这种'用数学规律替代模拟'的思路在实际开发中很有用:

    场景一:约瑟夫问题

    经典的约瑟夫问题也是每隔 k 个人淘汰一人,最后剩下谁。同样可以不用模拟,而用递推或公式计算。

    场景二:分批处理

    当数据量巨大且处理规则有规律时(如每隔一批保留一批),可以像本题一样抽象成 head、step、count,避免真正构建和遍历整个序列。

    场景三:游戏逻辑

    某些回合制游戏每轮按固定规则淘汰角色,若规则呈现周期性或可归纳的数学规律,可以用类似方式在 O(log n) 时间内算出结果。

    总结

    本题的关键在于发现:每轮消除后剩余数字构成等差数列,只需维护头元素、步长和数量,就能在 O(log n) 时间和 O(1) 空间内得到最终结果。

    要点总结:

    1. 从左到右消除:head 一定增加 step
    2. 从右到左消除:仅在 count 为奇数时 head 增加 step
    3. 每轮 step 翻倍,count 减半

    算法优势:

    • 时间复杂度 O(log n),适合 n 极大的情况
    • 空间复杂度 O(1)
    • 逻辑简洁,易于实现

    目录

    1. 文章目录
    2. 摘要
    3. 描述
    4. 题解答案
    5. 题解代码分析
    6. 核心观察:等差数列
    7. 从左到右消除时 head 的更新
    8. 从右到左消除时 head 的更新
    9. step 和 count 的更新
    10. 边界情况
    11. 完整执行流程示例(n=9)
    12. 示例测试及结果
    13. 示例 1:n = 9
    14. 示例 2:n = 1
    15. 示例 3:n = 6
    16. 示例 4:n = 2
    17. 时间复杂度
    18. 空间复杂度
    19. 实际应用场景
    20. 场景一:约瑟夫问题
    21. 场景二:分批处理
    22. 场景三:游戏逻辑
    23. 总结
    • 免费图片AI生成工具免费生成了解详情
    • Magick API 一键接入全球大模型注册送1000万token查看
    • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
    • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
    • 100+免费在线小游戏爽一把
    极客日志微信公众号二维码

    微信扫一扫,关注极客日志

    微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

    更多推荐文章

    查看全部
    • Cursor 辅助开发 Web 背单词应用实战
    • C++ 设计模式详解:分类、实现与核心应用
    • Elasticsearch 核心概念与 Java 客户端实战指南
    • Git 多用户提交身份配置与切换方法
    • Spring Boot 参数配置详解:properties、yml 及外部化配置
    • OpenClaw 配置 GLM-4.7 Flash+DuckDuckGo 实现飞书机器人联网问答
    • Linux 是什么?
    • macOS 下使用脚本安装 Homebrew 及配置国内镜像源
    • 基于 Docker 与 Ollama 本地部署 DeepSeek 大模型指南
    • FPGA 温度采集系统设计:MAX6675 驱动与 Qt 上位机曲线绘制
    • 论文写作 AI 辅助:从选题到定稿,我踩过的坑和总结
    • 无需拓展即可在 Copilot 接入第三方 OpenAI 接口
    • 从 Alpaca 到 Vicuna:用 Llama Factory 切换对话模板
    • Flutter 应用架构从入门到可扩展的演进实践
    • Java 链表原理与 LinkedList 核心用法解析
    • 渗透测试基础概念、流程与内网渗透技术详解
    • ClawX 可视化 AI 智能体使用指南
    • DOM Testing Library 异步测试实战:waitFor 与 waitForElementToBeRemoved
    • C++ 中 stack 类的实现原理与接口详解
    • AIGC 背景下图文内容社区数据指标体系构建指南

    相关免费在线工具

    • 加密/解密文本

      使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

    • Gemini 图片去水印

      基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

    • Base64 字符串编码/解码

      将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

    • Base64 文件转换器

      将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

    • Markdown转HTML

      将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

    • HTML转Markdown

      将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online