牛客网3704题:解密约瑟夫环

贾蔷
• 阅读 2

牛客网3704题:解密约瑟夫环

引言:一个有趣的儿童游戏

每年六一儿童节,牛客网都会组织小朋友们玩一个特别的游戏:n个小朋友围成一圈,从编号0开始报数,数到m-1的小朋友出列并获得礼物,然后从下一位重新报数,直到剩下最后一位幸运儿。这个看似简单的游戏背后,隐藏着计算机科学中著名的‌约瑟夫环问题‌。

一、问题定义与暴力解法

1.问题描述

给定n个排成圆圈的人(编号0到n-1)和一个数字m,从第0人开始报数,每数到第m个人就将其淘汰,求最后剩下的人的初始编号。

2.暴力模拟

最直观的解法是模拟整个过程:

int lastRemaining(int n, int m) {    vector<bool> out(n, false); // 标记是否出局
    int current = 0; // 当前报数位置
    int remain = n;  // 剩余人数

    while (remain > 1) {        int count = 0;        while (count < m) {            if (!out[current]) {
                count++;
            }
            current = (current + 1) % n;
        }
        out[(current - 1 + n) % n] = true;
        remain--;
    }    
    for (int i = 0; i < n; ++i) {        if (!out[i]) return i;
    }    return -1;
}

‌时间复杂度分析‌:O(nm),当n和m都很大时效率极低

3.数学递推解法

约瑟夫环问题有一个优美的数学解法。设f(n,m)表示n个人报数m时的解:

  1. 当n=1时,f(1,m)=0(只有一个人)
  2. 对于n>1,f(n,m)=(f(n-1,m)+m)%n

递推过程解析

这个公式的直观理解是:当一个人被淘汰后,问题规模缩小为n-1,新的起点是被淘汰者的下一位。我们需要将n-1的解映射回原始编号。

‌示例演算‌(n=5,m=3):

  1. f(1,3)=0
  2. f(2,3)=(f(1,3)+3)%2=(0+3)%2=1
  3. f(3,3)=(f(2,3)+3)%3=(1+3)%3=1
  4. f(4,3)=(f(3,3)+3)%4=(1+3)%4=0
  5. f(5,3)=(f(4,3)+3)%5=(0+3)%5=3

高效实现

基于递推公式的解法:

int lastRemaining(int n, int m) {    if (n < 1 || m < 1) return -1;    int last = 0; // f(1,m)=0
    for (int i = 2; i <= n; ++i) {
        last = (last + m) % i;
    }    return last;
}

‌复杂度分析‌:

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

二、算法优化与变种

优化方向

  1. 当m远大于n时,可以先计算m%n减少迭代次数
  2. 对于超大n的情况,可以寻找数学规律进一步优化

变种问题

  1. 双向约瑟夫环(顺时针和逆时针交替)
  2. 每次m变化的约瑟夫问题
  3. 找出淘汰顺序而不仅是最后幸存者

三、完整代码:

class Solution {
  public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param n int整型
     * @param m int整型
     * @return int整型
     */
    int LastRemaining_Solution(int n, int m) {
        if (n < 1 || m < 1) return -1; // 处理非法输入

        int last = 0; // n=1时的解

        // 递推公式:f(n,m)=(f(n-1,m)+m)%n
        for (int i = 2; i <= n; ++i) {
            last = (last + m) % i;
        }

        return last; // 返回最后剩下的数字
    }
};

四、总结

从暴力模拟到数学递推,约瑟夫环问题展示了算法优化的重要性。理解这个问题的数学本质,不仅能解决具体编程问题,更能培养抽象思维和数学建模能力。记住这个优雅的递推公式:f(n,m)=(f(n-1,m)+m)%n,它将O(nm)的时间复杂度优化到了O(n)。

来源:牛客题解

点赞
收藏
评论区
推荐文章
美凌格栋栋酱 美凌格栋栋酱
6个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Stella981 Stella981
3年前
Python字符串格式化
1.简单运用字符串类型格式化采用format()方法,基本使用格式是:转自<模板字符串.format(<逗号分隔的参数)调用format()方法后会返回一个新的字符串,参数从0开始编号。"{}:计算机{}的CPU占用率为{}%。".format("20161231","PYTHON",10)Out\10\:'2
Wesley13 Wesley13
3年前
JAVA游戏编程学习笔记(四)Java PinBall 简单弹球小游戏【1】
之前写了一个非常简单的Java2D小游戏底层框架,为了把这个游戏框架丰富起来,这阵子需要实际完成几个小游戏代码,这样才能在实际中检验游戏框架使用性!先来一个简单的小游戏:JavaPinBall简单弹球小游戏 先上图!!(http://static.oschina.net/uploads/space/2015/1110/202955_
Stella981 Stella981
3年前
LayaAir:用3D项目演示老项目如何适配微信小游戏
在QQ上线玩一玩后,引擎部同事彻夜鏖战,刚刚终于上线了1.7.15beta版。推出了QQ玩一玩与微信小游戏的一键发布功能。小编送上一篇刚出炉的技术干货,希望能给开发者带来帮助。之前有介绍过微信小游戏的创建与调试全流程,从1.7.15beta开始,这个流程更加完善,尤其是TS与JS的开发者,也可以做到一键发布微信小游戏了。本篇尽可
Wesley13 Wesley13
3年前
C 语言实例
30个人在一条船上,超载,需要15人下船。于是人们排成一队,排队的位置即为他们的编号。报数,从1开始,数到9的人下船。如此循环,直到船上仅剩15人为止,问都有哪些编号的人下船了呢?include<stdio.hintc0;inti1;intj0;
Stella981 Stella981
3年前
HDOJ 2100 Lovekey
ProblemDescriptionXYZ26进制数是一个每位都是大写字母的数字。A、B、C、…、X、Y、Z分别依次代表一个0~25的数字,一个n位的26进制数转化成是10进制的规则如下A0A1A2A3…An1的每一位代表的数字为a0a1a2a3…an1,则该XYZ26进制数的10进制值就为m=a0\26
Stella981 Stella981
3年前
Hibernate纯sql查询结果和该sql在数据库直接查询结果不一致
问题:今天在做一个查询的时候发现一个问题,我先在数据库实现了我需要的sql,然后我在代码中代码:selectdistinctd.id,d.name,COALESCE(c.count_num,0),COALESCE(c.count_fix,0),COALESCE(c
公孙晃 公孙晃
1年前
中文版:机械迷城Machinarium珍藏版「Mac」
是一款充满挑战和惊喜的冒险解谜游戏,无论是喜欢解谜类型的玩家,还是喜欢机械风格和音乐的玩家,都会在这款游戏中找到乐趣。在Machinarium中,玩家将扮演一个名叫Josef的小机器人,他和他的机器女友被拖到了城市外围,需要通过冒险解谜,重新回到城市中心并
贾蔷 贾蔷
1个月前
牛客13279题解:利用递归与深度优先搜索计算树的最大高度(附完整代码)
一、题目解读牛客13279题要求计算给定树的最大高度。题目输入一棵以邻接表形式表示的树(节点从0开始编号),需要输出从根节点到最深叶节点的最长路径长度。树的结构由n个节点和n1条边构成,保证为连通无环图。理解题目核心在于准确获取树的拓扑关系,并设计算法遍历
贾蔷 贾蔷
2星期前
【动态规划入门】力扣509题:斐波那契数列的经典解法与优化思路
一、题目解读‌是一个经典的数学问题,在计算机科学中常被用作教学的入门案例。这个神奇的数列从0和1开始,后续每个数字都是前两个数字之和。题目要求我们计算第n个,看似简单的问题背后却蕴含着重要的算法思想。当n较小时,这个问题似乎微不足道,但随着n的增大,不同的