C++版 - 剑指Offer 面试题45:圆圈中最后剩下的数字(约瑟夫环问题,ZOJ 1088:System Overload类似)题解

剑指Offer 面试题45:圆圈中最后剩下的数字(约瑟夫环问题)

原书题目:0, 1, … , n-1 这n个数字排成一个圈圈,从数字0开始每次从圆圏里删除第m个数字。求出这个圈圈里剩下的最后一个数字。

牛客网改编:孩子们的游戏(圆圈中最后剩下的数)

参与人数:1699  时间限制:1秒 空间限制:32768K
本题知识点: 模拟

题目描述
每年六一儿童节,NowCoder都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为NowCoder的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去....直到剩下最后一个小朋友,可以不用表演,并且拿到NowCoder名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼品呢?


方法1: 用环形单链表来模拟圆圈
可转换为带环单链表删除结点的问题:创建一个总共有n个结点的环形链表,然后每次在这个链表中删除第m个结点。

时间复杂度为O(n*m),空间复杂度为O(n).


方法2:映射,找规律
时间复杂度为O(n),空间复杂度为O(1).
此题只需求最后一个报数胜利者,我们可以用数学归纳法解决该问题,为了讨论方便,先把问题稍微改变一下,并不影响原意:

问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。求胜利者的编号。

分析:

我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开始):
        k  k+1  k+2  ... n-2, n-1, 0, 1, 2, ... k-2并且从k开始报0。
现在我们把他们的编号做一下转换:
k       --> 0
k+1   --> 1
k+2   --> 2
...
k-2   --> n-2
k-1   --> n-1
变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解: 例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗?!!变回去的公式很简单,不难推导出:x'=(x+k)%n。
令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n]。


相应的递推关系为:

f(1)=0;
f(n)=(f(n-1)+m)%n;  (n>1, n∈N)
有了这个公式,我们要做的就是从1-n顺序算出f(i)的数值,最后结果是f(n)。 因为实际生活中编号总是从1开始,我们输出f(n)+1。

注意:此题中m是不变的量,从刚开始输入后就一直不变,而n逐一减小...


方法2

递归实现 AC代码:

class Solution {
public:
    int LastRemaining_Solution(int n, int m)
    {
        if(n<1 || m<1) return -1;
        if(n==1) return 0;
        return (LastRemaining_Solution(n-1,m)+m)%n;        
    }
};

class Solution {
public:
    int LastRemaining_Solution(unsigned int n, unsigned int m)
    {
        if(n==0 || m==0) return -1;
        if(n==1) return 0;
        return (LastRemaining_Solution(n-1,m)+m)%n;        
    }
};

方法2
迭代实现 AC代码:
class Solution {
public:
    int LastRemaining_Solution(unsigned int n, unsigned int m)
    {
        if(n==0 || m==0) return -1;
        if(n==1) return 0;
        int last=0;  // f(1,x)=0
        
        for(unsigned int i=2; i<=n; i++)
        {
            last=(last+m)%i;
        }
        return last;
    }
};

需要注意的是:newcoder OJ上给的编号是从0开始的,而不是从1开始的,输入不合法时要求返回-1 .


ZOJ上也有相关的题:


ZOJ Problem Set - 1088


提交网址: http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=88


Time Limit: 10 Seconds      Memory Limit: 32768 KB


Recently you must have experienced that when too many people use the BBS simultaneously, the net becomes very, very slow.
To put an end to this problem, the Sysop has developed a contingency scheme for times of peak load to cut off net access for some buildings of the university in a systematic, totally fair manner. Our university buildings were enumerated randomly from 1 to n. XWB is number 1, CaoGuangBiao (CGB) Building is number 2, and so on in a purely random order.
Then a number m would be picked at random, and BBS access would first be cut off in building 1 (clearly the fairest starting point) and then in every mth building after that, wrapping around to 1 after n, and ignoring buildings already cut off. For example, if n=17 and m=5, net access would be cut off to the buildings in the order [1,6,11,16,5,12,2,9,17,10,4,15,14,3,8,13,7]. The problem is that it is clearly fairest to cut off CGB Building last (after all, this is where the best programmers come from), so for a given n, the random number m needs to be carefully chosen so that building 2 is the last building selected.

Your job is to write a program that will read in a number of buildings n and then determine the smallest integer m that will ensure that our CGB Building can surf the net while the rest of the university is cut off.

Input Specification

The input file will contain one or more lines, each line containing one integer  n  with 3 <=  n  < 150, representing the number of buildings in the university.
Input is terminated by a value of zero (0) for  n .

Output Specification

For each line of the input, print one line containing the integer  m  fulfilling the requirement specified above.

Sample Input

3
4
5
6
7
8
9
10
11
12
0

Sample Output

2
5
2
4
3
11
2
3
8
16


AC代码(PHP实现):

<?php
function judge($m, $n)
{
    $from = 0;
    for($i = 2; $i < $n; $i++)
      $from = ($from + $m)%($i);
    if($from + 1 == 1) return 1;
    else return 0;
}
	$fd = STDIN;  // STDIN是oj提供的常量,等价于 fopen("php://stdin","r"));
	while(fscanf($fd, "%d", $n)==1 && $n!=0)
	{
	    $m = 1;
	    while(!judge($m, $n))
	    {
	        ++$m;
	    }
	    echo $m."\n";
	}


带测试的PHP代码,请参考本人博文:详解OJ(Online Judge)中PHP代码的提交方法及要点【举例:ZOJ 1001 (A + B Problem)】 的末尾部分...


相关链接:

http://www.cnblogs.com/AndyJee/p/4687715.html


全部评论

相关推荐

bg双非本科,方向是嵌入式。这次秋招一共拿到了&nbsp;8&nbsp;个&nbsp;offer,最高年包&nbsp;40w,中间也有一段在海康的实习经历,还有几次国家级竞赛。写这篇不是想证明什么,只是想把自己走过的这条路,尽量讲清楚一点,给同样背景的人一个参考。一、我一开始也很迷茫刚决定走嵌入式的时候,其实并没有一个特别清晰的规划。网上的信息很零散,有人说一定要懂底层,有人说项目更重要,也有人建议直接转方向。很多时候都是在怀疑:1.自己这种背景到底有没有机会2.现在学的东西到底有没有用3.是不是已经开始晚了这些问题,我当时一个都没答案。二、现在回头看,我主要做对了这几件事第一,方向尽早确定,但不把自己锁死。我比较早就确定了嵌入式这个大方向,但具体做哪一块,是在项目、竞赛和实习中慢慢调整的,而不是一开始就给自己下结论。第二,用项目和竞赛去“证明能力”,而不是堆技术名词。我不会刻意追求学得多全面,而是确保自己参与的每个项目,都能讲清楚:我负责了什么、遇到了什么问题、最后是怎么解决的。第三,尽早接触真实的工程环境。在海康实习的那段时间,对我触动挺大的。我开始意识到,企业更看重的是代码结构、逻辑清晰度,以及你能不能把事情说清楚,而不只是会不会某个知识点。第四,把秋招当成一个需要长期迭代的过程。简历不是一次写完的,面试表现也不是一次就到位的。我会在每次面试后复盘哪些问题没答好,再针对性补。三、我踩过的一些坑现在看也挺典型的:1.一开始在底层细节上纠结太久,投入产出比不高2.做过项目,但前期不会总结,导致面试表达吃亏3.早期有点害怕面试,准备不充分就去投这些弯路走过之后,才慢慢找到节奏。四、给和我背景相似的人一点建议如果你也是双非,准备走嵌入式,我觉得有几件事挺重要的:1.不用等“准备得差不多了”再投2.项目一定要能讲清楚,而不是做完就算3.不要只盯着技术,多关注表达和逻辑很多时候,差的不是能力,而是呈现方式。五、写在最后这篇总结不是标准答案,只是我个人的一次复盘。后面我会陆续把自己在嵌入式学习、竞赛、实习和秋招中的一些真实经验拆开来讲,希望能对后来的人有点帮助。如果你正好也在这条路上,希望你能少走一点弯路。
x_y_z1:蹲个后续
点赞 评论 收藏
分享
评论
1
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务