剑指offer-46、孩⼦们的游戏(圆圈中最后剩下的数)

题目描述

有个游戏是这样的:⾸先,让 n 个⼩朋友们围成⼀个⼤圈,⼩朋友们的编号是0~n-1。然后,随机指定⼀个数 m ,让编号为0的⼩朋友开始报数。每次喊到 m-1 的那个⼩朋友要出列唱⾸歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下⼀个⼩朋友开始,继续 0... m-1报数....这样下去....直到剩下最后⼀个⼩朋友,可以不⽤表演,并且拿到⽜客礼品,请你试着想下,哪个⼩朋友会得到这份礼品呢?

示例 输⼊:5,3 输出:2

思路及解答

数组模拟

通过布尔数组标记小朋友的出局状态

java 复制代码
public class Solution {

    public int lastRemaining(int n, int m) {
        if (n <= 0 || m <= 0) return -1;
        
        boolean[] out = new boolean[n]; // 标记是否出局
        int count = n;                  // 剩余人数
        int index = 0;                  // 当前报数位置
        int step = 0;                   // 报数计数器
        
        while (count > 1) {
            // 如果当前小朋友未出局,参与报数
            if (!out[index]) {
                step++;
                // 报到m-1的小朋友出局
                if (step == m) {
                    out[index] = true;  // 标记出局
                    count--;            // 剩余人数减1
                    step = 0;           // 重置计数器
                }
            }
            // 移动到下一个位置(循环)
            index = (index + 1) % n;
        }
        
        // 找到最后一个未出局的小朋友
        for (int i = 0; i < n; i++) {
            if (!out[i]) {
                return i;
            }
        }
        return -1;
    }
}
  • 时间复杂度:O(n×m),最坏情况下每个小朋友都需要报数m次
  • 空间复杂度:O(n),需要长度为n的布尔数组

循环链表

使用循环链表模拟小朋友围成的圈,将小朋友存入链表,循环删除第m个元素

java 复制代码
public class Solution {

    public int lastRemaining(int n, int m) {
        if (n <= 0 || m <= 0) return -1;
        
        List<Integer> list = new LinkedList<>();
        // 初始化链表,存入所有小朋友编号
        for (int i = 0; i < n; i++) {
            list.add(i);
        }
        
        int index = 0; // 当前指针位置
        
        while (list.size() > 1) {
            // 计算要删除的位置:(当前索引 + m-1) % 当前大小
            index = (index + m - 1) % list.size();
            list.remove(index);
            // 删除后index自动指向下一个元素,不需要移动
        }
        
        return list.get(0);
    }
}
  • 时间复杂度:O(n×m),需要遍历链表进行删除操作
  • 空间复杂度:O(n),需要存储n个节点

数学归纳法(推荐)

分析每次被删除的数字规律,直接计算出最后的数字,不需要模拟

java 复制代码
F(N,M) = ( F(N−1,M) + M ) % N

递推公式的推导过程:

  1. 第一次删除:从0开始报数,删除第(m-1)%n个小朋友
  2. 重新编号 :删除后,从第m%n个小朋友开始重新编号:
    • 旧编号:m%n, m%n+1, ..., n-1, 0, 1, ..., m%n-1
    • 新编号:0, 1, 2, ..., n-2
  3. 映射关系:新编号x对应的旧编号为(x + m) % n

示例验证(n=5, m=3):

text 复制代码
原始编号: 0, 1, 2, 3, 4
第一次删除编号2 → 剩余: 0, 1, 3, 4
重新编号: 3→0, 4→1, 0→2, 1→3
f(5,3) = (f(4,3) + 3) % 5
java 复制代码
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if (n <= 0 || m <= 0) {
            return -1;
        }
        int result = 0;
        for (int i = 2; i <= n; i++) {
            result = (result + m) % n;
        }
        return result;
    }
}
  • 时间复杂度:O(n),需要n次递归调用
  • 空间复杂度:O(n),递归调用栈深度

迭代优化

将递归转为迭代,避免栈溢出风险,是生产环境的最佳选择

java 复制代码
public class Solution {

    public int lastRemaining(int n, int m) {
        if (n <= 0 || m <= 0) return -1;
        
        int result = 0; // f(1, m) = 0
        
        // 从2个人情况开始,逐步计算到n个人
        for (int i = 2; i <= n; i++) {
            result = (result + m) % i;
        }
        
        return result;
    }
}
  • 时间复杂度:O(n),只需一次循环
  • 空间复杂度:O(1),只使用常数空间
相关推荐
lulu121654407818 小时前
OpenRouter Fusion 多模型融合架构深度拆解:预算级模型组团打平 Fable 5,多模型协作才是 AGI 的正确打开方式?
java·人工智能·架构·ai编程·agi
雨辰AI18 小时前
生产级实测:SpringBoot3 + 达梦数据库接口从 200ms 优化至 20ms 完整调优指南
java·数据库·spring boot·后端·政务
(Charon)19 小时前
【C++ 面试高频:内存管理、RAII 和智能指针详解】
java·开发语言·word
凡人叶枫19 小时前
Effective C++ 条款39:明智而审慎地使用 private 继承
java·数据库·c++·嵌入式开发
轻刀快马19 小时前
跨越软硬件的共鸣(二):从 Cache 写策略看 Redis 与 DB 的一致性博弈
java·开发语言·redis·计算机组成原理
折哥的程序人生 · 物流技术专研19 小时前
Java 23 种设计模式:从踩坑到精通 | 装饰器模式 —— 比继承更灵活的扩展方式,你用过吗?
java·装饰器模式·java面试·结构型模式·java设计模式·javaio·从踩坑到精通
lili001219 小时前
2026 企业 AI 选型新范式:OpenRouter Fusion 证明多模型融合性价比远超单模型,企业该如何重构技术栈? - 微元算力(weytoken)
java·人工智能·python·重构·ai编程
shushangyun_19 小时前
汽车服务行业B2B平台+AI解决方案哪家专业:2026年最新测评
java·运维·网络·数据库·人工智能·汽车
A.说学逗唱的Coke19 小时前
【大模型专题】Spring AI Alibaba × Skill 整合实战:让 AI 真正“会干活
java·人工智能·spring
大黄说说20 小时前
深入理解 Go 协程 Goroutine:并发编程的核心精髓
java·数据库·python