约瑟夫环
题目描述
此题来源于洛谷P1996
核心思路
问题:如何理解j=a[j]呢?
如何理解 a [ j ] = a [ a [ j ] ] a[j]=a[a[j]] a[j]=a[a[j]]呢?
出局图示如下:
代码
#include<iostream>
#include<algorithm>
using namespace std;
const int N=110;
int a[N]; //a[i]存放的是当前节点i的下一个节点的位置下标
int b[N]; //出局序列 b[i]这个值表示的是出局那个人的编号
int n,m;
int main()
{
scanf("%d%d",&n,&m);
//a[1]=2,即当前节点1号它的下一个节点的位置下标是2
for(int i=1;i<n;i++) //模拟链表
a[i]=i+1;
a[n]=1; //第n个人指向第1人,形成一个环
int k=1; //记录每一个m间隔中此时已经数了多少个人
int j=n; //让j落到最后 这样当j=a[j]时就可以从1开始了
for(int i=1;i<=n;i++) //n个人都出局为止
{
//报数,当还没有报到m时,就继续
while(k<m)
{
j=a[j];//j移动到下一个节点
k++; //报数个数+1
}
//退出循环后,说明此时正好报到m,因此此时编号为a[j]的这个人就要出局
b[i]=a[j];
//由于数到m的那个节点出局(即当前节点j的下一个节点a[j]出局了),此时让a[j]=a[a[j]]
//那么此时当前节点j的下一个节点就当前节点j的下一个节点的下一个节点
a[j]=a[a[j]];
k=1; //重置k为1
//printf("%d出局 a[%d]=%d\n",b[i],j,a[j]);
}
//输出这n个人的出局序列
for(int i=1;i<=n;i++)
printf("%d ",b[i]);
return 0;
}
题目描述
核心思路
我们可以给每一个人一个编号(索引值),每个人用字母来代替,下面举例 n = 8 , m = 3 n=8,m=3 n=8,m=3时,我们定义 f [ n ] [ m ] f[n][m] f[n][m]表示最后剩下的那个人的编号,因此我们只关系最后剩下来这个人的索引号的变化情况即可。
当某个人出队后,它的下一个人就成为了队首,而不再是原来的队首了。如下图,当C出队后,它的下一个人就是D,那么D就会成为队首,而不是让A成为队首。模拟发现,最终G是胜利者。
从8个人开始,每次杀掉一个人,去掉被杀的人,然后把杀掉那个人之后的第一个人作为开头重新编号
- 第一次C被杀掉,人数变成7,D作为开头,(最终活下来的G的编号从6变成3)
- 第二次F被杀掉,人数变成6,G作为开头,(最终活下来的G的编号从3变成0)
- 第三次A被杀掉,人数变成5,B作为开头,(最终活下来的G的编号从0变成3)
- 第四次E被杀掉,人数变成4,G作为开头,(最终活下来的G的编号从3变成0)
- 第五次B被杀掉,人数变成3,D作为开头,(最终活下来的G的编号从0变成1)
- 第六次H被杀掉,人数变成2,D作为开头,(最终活下来的G的编号从1变成1)
- 第七次D被杀掉,人数变成1,G作为开头,(最终活下来的G的编号从1变成0)
由此可见,当只剩一个人时,这个人就是胜利者,他的编号肯定为0。
问题:如何由最终的胜利者的编号反推呢?
现在我们知道了G的索引号的变化过程,那么我们反推一下,从 n = 7 n=7 n=7到 n = 8 n=8 n=8的过程,怎样才能将 n = 7 n=7 n=7的排列顺序变为 n = 8 n=8 n=8时的排列顺序呢?我们【先把被杀掉的C补充回来,然后右移m个人,发现溢出了,再把溢出的补充在最前面】,经过这般操作后,就可以恢复到 n = 8 n=8 n=8时的排列顺序了。当删除某个节点时,这个节点后面的全部节点都向前平移 m m m个距离;当恢复某个节点时,那么全部节点都向后移动 m m m个单位,然后如果某些节点出界了,那么就然这些节点回到前面(设当前这轮有 x x x个人,那么如果越界的话,就让那些越界的人 % x \%x %x就可以回到前面了)。
因此,我们可以推导出递归关系式 f ( 8 , 3 ) = [ f ( 7 , 3 ) + 3 ] % 8 f(8,3)=[f(7,3)+3]\%8 f(8,3)=[f(7,3)+3]%8,推广到一般情况就是 f ( n , m ) = [ f ( n − 1 , m ) + m ] % n f(n,m)=[f(n-1,m)+m]\%n f(n,m)=[f(n−1,m)+m]%n。
递推公式:
再把n=1
这个最初的情况加上,就得到递推公式:
f ( n , m ) = { 0 n = 1 [ f ( n − 1 , m ) + m ] % n n > 1 f(n,m)=\begin {cases} 0 & n=1 \\ [f(n-1,m)+m]\%n & n>1 \end{cases} f(n,m)={0[f(n−1,m)+m]%nn=1n>1
代码
class Solution {
public:
int lastRemaining(int n, int m) {
int pos = 0; // 最终活下来那个人的初始位置
//i表示这一轮的人数 一直做到最初有n个人时 最终胜利者的位置
for(int i = 2; i <= n; i++)
{
pos = (pos + m) % i; // 每次循环右移
}
//这题的编号是从0开始,因此直接返回数组下标pos就是正确的编号
//如果题目的编号是从1开始,但是由于我们的数组下标是从0开始,那么此时输出答案时就要数组下标pos+1,才能转成编号
return pos;
}
};