【算法】约瑟夫环问题求解

【算法】约瑟夫环问题求解

文章目录

  • 【算法】约瑟夫环问题求解
      • 0.约瑟夫环问题描述
      • 1.模拟数组解法
      • 2.递推求解
      • 3.链表解法
      • 4.LeetCode题目

0.约瑟夫环问题描述

已知n个人(编号1,2,3,…,n表示)围坐在一张圆桌周围。从编号为1的人开始报数,数到k的人出列;与他相邻的下一个人又从1开始报数,数到k的人又出列;依此规律重复,直到所有人出列,求最后一个出列的人。

1.模拟数组解法

思路:将所有元素标识初始化为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

2.递推求解

详细递推过程如下
已知队里有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];
}

3.链表解法

类似于数组法,不过不适用辅助标志数组,而是直接使用链表来表示剩余的人,只剩一个时即是最后的结果。

// 链表解法
#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

4.LeetCode题目

LeetCode上的约瑟夫环问题 1823-findTheWinner


“如果你不能简单地解释某一概念,说明你没有很好地掌握它。”——艾尔伯特·爱因斯坦

你可能感兴趣的:(c语言,算法,开发语言)