已知n个人(编号1,2,3,…,n表示)围坐在一张圆桌周围。从编号为1的人开始报数,数到k的人出列;与他相邻的下一个人又从1开始报数,数到k的人又出列;依此规律重复,直到所有人出列,求最后一个出列的人。
思路:将所有元素标识初始化为0,每次将报到k的值置为1,下一轮不再加入计数。
使用sum计数,每次移除一个,sum加一,直到sum为n-1时退出循环。
// 模拟数组解法
int josephus_solver(int a[], int size, int k)
{
// 数组无效时,返回-1作为提示,因为数组中的元素均为正数
if (a==NULL || size<=0)
return -1;
// i为数组索引
int i = 0;
// 用一个removed数组作为每个元素是否被移除的标识,1表示移除
int *removed = (int *)malloc(sizeof(int)*size);
memset(removed, 0, sizeof(int)*size);
int j = 0; // 用来模拟报数
int sum = 0; // 统计被移除的元素数量,当只剩一个元素时终止
i = 0;
while (sum
详细递推过程如下
已知队里有n个人,编号为: 0, 1, 2, …, n-1
进行一轮报数,报到k的人出队,第一次出队的人编号必为(k-1)%n
这时我们可以把队伍编号记为: 0, 1, 2, …, k-2, k-1, k, …, n-1
将k-1标记为X,表示已经出队。 0, 1, 2, …, k-2, X , k, …, n-1
重新编号让k为0,则有: n-k, n-k+1, n-k+2, …, n-2, (X), 0, 1, …, n-1-k
此时的编号与前一轮之间的关系不难看出来,记前一轮为index[n],表示前一轮中共有n个人,编号为0~(n-1)
当前的编号记为index[n-1],因为相比于前一轮被移除一个,并从k重新编号0~(n-2)
则编号之间的对应关系:index[n]=(index[n-1]+k)%n
我们可以一直推下去:有n-2人,有n-3人, …, 有1人。
这样就得到了递推式:index[n] = (index[n-1]+k)%n
当只有1人时:index[1] = 0
前一轮的编号=(新一轮的编号+k)%前一轮的人数
也可以理解为:新一轮编号向前移动k位再通过取模形成环即可得到前一轮的编号。
我们很容易知道最后一个出队的人的编号一定是0,因为此时只剩下他一个人。再通过上述的递推关系式,我们就能够得到他对应的第一轮的编号。
// 递推求解
// 时间复杂度为O(n),空间复杂度为O(1),只能求得最后出局的人。
int josephus_solver_recursive(int a[], int size, int k)
{
// 数组无效时,返回-1作为提示,因为数组中的元素均为正数
if (a==NULL || size<=0)
return -1;
// 最后一个出队的人编号一定是0,因为此时只剩下他一个人
int index = 0; // 当只有1人时编号为0的人最后出列
for (int i=2; i<=size; i++) {
index = (index+k)%i;
}
return a[index];
}
类似于数组法,不过不适用辅助标志数组,而是直接使用链表来表示剩余的人,只剩一个时即是最后的结果。
// 链表解法
#include
int josephus_solver_list(int n, int k)
{
std::list ltmp;
for(int i=1; i<=n; ++i)
ltmp.push_back(i);
auto pos = ltmp.begin();
while(ltmp.size() > 1) {
for(int i=1; i
LeetCode上的约瑟夫环问题 1823-findTheWinner
“如果你不能简单地解释某一概念,说明你没有很好地掌握它。”——艾尔伯特·爱因斯坦