约瑟夫环问题

一、问题描述

1.1 问题

  题目描述:

    • (1)编号为[1,2,…,n]的n个人按顺时针方向围坐一圈(一般给定一个数,从1~n)
    • (2)任选一个正整数作为报数上限m,从第一个人开始按顺时针方向从自1开始顺序报数,
    • (3)报到m时停止报数,报m的人出列
    • (4)从上一个出列得人顺时针方向上的下一个人开始重新从1报数
    • (5)如此下去

1.2 提问方式

  • (1)输出出队顺序
  • (2)输出最后一个出队的编号

二、输出出队顺序

  实现思想:利用双端队列模拟整个过程。代码如下:

 1 vector<int> getOrder(int n, int k)
 2 {
 3     deque<int> q;
 4     //开始将所有数字正确入队,1在队头,n在队尾
 5     for (int i = 1; i <= n; i++)
 6         q.push_back(i);
 7 
 8     //保存出队顺序
 9     vector<int> ans;
10 
11     //报数记录
12     int cnt = 0;
13 
14     //队列不为空就继续报数
15     while (!q.empty())
16     {
17         cnt++;
18         //如果当前报数等于k,就将其出队,并保存在结果中
19         if (cnt == k)
20         {
21             ans.push_back(q.front());
22             q.pop_front();
23             //重置报数
24             cnt = 0;
25         }
26         else
27         {
28             //如果当前报数不等于k,将其插队队尾,等待下一轮的报数
29             q.push_back(q.front());
30             q.pop_front();
31         }
32     }
33 
34     //返回结果
35     return ans;
36 }

 

三、最后出队的数字

  思路:反向模拟!

  进行公式推算:由于我们需要求最后一个出队人的编号,而最后一个出队人的编号一定为1,所以我们需要反推,也就是说通过新编号推算得到旧编号!

 

    • 旧编号 --> 新编号:(旧编号 - 报数值)% 旧人数
    • 新编号 --> 旧编号:(新编号 + k)% 旧人数,也就是old_index = (new_index + k) % old_number

 

  最后,我们经过n-1次的公式计算即可反推得到最后一个出队人的本来的序号

  需要注意的是,上述推导的公式必须从0开始才有效也就是说我们将上述图中所有的值都减去1,最后输出时再加上1即可。

  代码如下:

 1 int getLastNum(int n, int k)
 2 {
 3     //新编号,且最后一个人的编号,无论如何都是0。
 4     int index = 0;
 5 
 6     //i代表上一轮剩余的人数
 7     for (int i = 2; i <= n; i++)
 8     {
 9         index = (index + k) % i;
10     }
11 
12     //返回编号
13     return index + 1;
14 }

四、参考文章

http://182.92.190.128/archives/suan-fa-12--yue-se-fu-huan-wen-ti

posted @ 2021-09-08 16:02  Mr-xxx  阅读(122)  评论(0编辑  收藏  举报