一群猴子要选新猴王。新猴王的选择方法是:让 N 只候选猴子围成一圈,从某位置起顺序编号为 1~N 号。从第 1 号开始报数,每轮从 1 报到 3,凡报到 3 的猴子即退出圈子,接着又从紧邻的下一只猴子开始同样的报数。如此不断循环,最后剩下的一只猴子就选为猴王。请问是原来第几号猴子当选猴王?
输入格式:
输入在一行中给一个正整数 N(≤ 1000)。
输出格式:
在一行中输出当选猴王的编号。
输入样例:
11
结尾无空行
输出样例:
7
结尾无空行
来源:
来源:PTA | 程序设计类实验辅助教学平台
链接:https://pintia.cn/problem-sets/14/exam/problems/808
提交:
题解:
模拟整个选王过程最直观,即构建一个长度为 N 的链表,各节点值为对应的顺序索引;每轮删除第 3 个节点,直至链表长度为 1 时结束,返回最后剩余节点的值即可。模拟法需要循环删除 n - 1 轮,每轮在链表中寻找删除节点需要 m 次访问操作(链表线性遍历),因此总体时间复杂度为 O(nm)。
实际上,本题是著名的 “约瑟夫环” 问题,可使用动态规划解决,将时间复杂度降至 O(n)。
当输入 N = 5 时模拟一下选王过程以理清思路,找出规律(此处假设猴子编号为 0 ~ N-1):
轮数 | 数组 | 开始元素 | 移除元素 |
---|---|---|---|
1 | [0, 1, 2, 3, 4] | 0 | 2 |
2 | [3, 4, 0, 1] | 3 | 0 |
3 | [1, 3, 4] | 1 | 4 |
4 | [1, 3] | 1 | 1 |
经过模拟的四轮删除过程,3 即是最后剩下的数字。接下来从最后剩下的 3 倒着看,我们可以反推出这个数字在之前每个轮次的位置:
-
第一轮反推,补上 3 个位置,然后模上当时的数组大小 2,位置是 (0 + 3) % 2 = 1;
-
第二轮反推,补上 3 个位置,然后模上当时的数组大小 3,位置是 (1 + 3) % 3 = 1;
-
第三轮反推,补上 3 个位置,然后模上当时的数组大小 4,位置是 (1 + 3) % 4 = 0;
-
第四轮反推,补上 3 个位置,然后模上当时的数组大小 5,位置是 (0 + 3) % 5 = 3;
总结一下反推的过程:数字在之前每个轮次的位置 = (当时 index + 3) % 当时数组大小
题目需要求出最后剩下的数字(选为猴王),则最后剩下元素的数组下标一定为 0,故反推过程下标初始化为 0。又因为最后一轮剩下 2 个元素即当时的数组大小为 2,则从 2 开始反推,直到达到 N 的长度即可反推出最后剩余数字在数组中的下标。需要注意的是模拟删除和反推的过程中猴子的编号是从 0 ~ N-1, 而题目中猴子编号为 1 ~ N,故最后打印结果时需加 1。
#include<stdio.h>
int main(void) {
int N;
scanf("%d", &N);
int index = 0;
for (int i = 2; i <= N; i++) {
// 数字在之前每个轮次的位置 = (当前 index + 3) % 上一轮剩余数字的个数
index = (index + 3) % i;
}
printf("%d", index + 1);
return 0;
}