不使用链表解决Josephus问题

还是Josephus问题,昨天用简单粗暴的链表方法得以解决,其时间复杂度为O(nm)。虽然能得以复现每个数字出列的顺序,但是在n和m较大的时候,能明显感觉到电脑正在艰难计算……

昨晚加上今早一直在琢磨:要想让电脑轻松一些,干活快一点,人脑就不能一点不动。如果仅仅需要Josephus问题的最终结果,而不关心中间的出列过程的话,是否可以不用链表这么笨重的东西?

仍然假设m<n。这回规则微调一下:编号从0开始,到n-1结束;报数也从0开始,那么报到m-1号的同学将要出列。

第1次报数,m-1号同学不幸出列,接下来要从m号开始从0报数,此时剩下n-1人。

其实,可以发现,如果这n-1个人当中的某个人足够幸运,留到了最后,那么他也是原先n个人当中的幸运儿,有点类似于"金子到哪都会发光"的道理。因此,规模为n的问题转化为了规模为n-1的问题。但需要像下面的映射一样重新为他们编号,左边是n个人时候的编号,右边是n-1个人时候的新编号。

m --> 0

m+1 --> 1

……

n-1 --> n-m-1

0 --> n-m

……

m-2 --> n-2

发现规律了,如果把规模为n时的编号叫"老编号",规模为n-1的编号叫"新编号"的话,那么

老编号 = (新编号 + m )% n

所以,如果找到幸运儿的新编号,那么按上面的公式就可以推出幸运儿的原始编号。

以此类推,接下来就要把n-1规模转化为n-2规模、n-3规模……直到只剩1个人,这时他的编号肯定是0,逆推回去就可以了。

这样的话,实际上只需要写一个循环就能解决问题了。最后不能忘记实际编号是从1开始,要记得+1。

输入输出部分与上一篇相同,就不贴了。核心部分的代码如下:

//返回Josephus问题中最后留下的数字
int SolveJosephus(unsigned int n, unsigned int m)
{
	unsigned int k, fk;

	fk = 0;
	k = 2;
	while(k <= n)
	{
		fk = (fk + m) % k;
		k++;
	}
	
	return fk+1; //实际的编号是从1开始的,而不是0
}
由于只有一个循环,因此该方法的时间复杂度仅为O(n),也就是说,与报数间隔m无关了。空间复杂度上来看,起码也节省了n*sizeof(Node)这么多内存。不过缺点呢,就是只能得到最后留下来的幸运数字,不能看到出列的过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值