首页 > 代码库 > 菜鸟系列之C/C++经典试题(五)

菜鸟系列之C/C++经典试题(五)

求圆圈中剩下的最后一个数字

题目:n个数字(0,1,…,n-1)形成一个圆圈,从数字0开始,每次从这个圆圈中删除第m个数字(第一个为当前数字本身,第二个为当前数字的下一个数字)。当一个数字删除后,从被删除数字的下一个继续删除第m个数字。求出在这个圆圈中剩下的最后一个数字。

本题就是著名的约瑟夫环问题。

本题的解法我们比较容易想到用链表,当然我们可以自己写一个链表,也可以直接用stl库中的list实现代码如下:

//使用标准库
int JosephusProblem_Solution2(int n, int m)
{
	if(n < 1 || m < 1)
		return -1;

	list<int> listInt;
	unsigned i;
	//初始化链表
	for(i = 0; i < n; i++)
		listInt.push_back(i);

	list<int>::iterator iterCurrent = listInt.begin();
	while(listInt.size() > 1)
	{
		//前进m - 1步
		for(i = 0; i < m-1; i++)
		{
			if(++iterCurrent == listInt.end())
				iterCurrent = listInt.begin();
		}
		//临时保存删除的结点
		list<int>::iterator iterDel = iterCurrent;
		if(++iterCurrent == listInt.end())
			iterCurrent = listInt.begin();
		//删除结点
		listInt.erase(iterDel);
	}

	return *iterCurrent;
}

分析:上面解法的时间复杂度为(n-1)*m,即(mn)。让我们试图来寻找一种更好的解法。

我们用归纳法来分析,假如我们能找到第n-1个数和第n个数关系,我们就可以一次递推到删除到1个数在2个数的位置,3….n个数中的文字,最后得到解。

现在我们来找这个关系:

在这n个数字中,第一个被删除的数字是(m-1)%n,为简单起见记为k。那么删除k之后的剩下n-1的数字为0,1,…,k-1,k+1,…,n-1,并且下一个开始计数的数字是k+1。相当于在剩下的序列中,k+1排到最前面,从而形成序列k+1,…,n-1,0,…k-1

k+1   ->    0
k+2    ->    1

n-1    ->    n-k-2
0       ->    n-k-1

k-1   ->   n-2

现在我们知道了有n-1个数时last的位置,记为f(n-1,m),那么如何来求得f(n,m)关于f(n-1,m)之间的关系?用X,Y来表示,如下:

Y             X

k+1   ->    0
k+2    ->    1

n-1    ->    n-k-2
0       ->     n-k-1

k-1    ->    n-2

y=( x+k+1) %n

k = (m-1)%n

所以y=(x+m)%n,最终关系如下:

               0                             n=1
f(n,m)={
               [f(n-1,m)+m]%n     n>1

所以,我们可以用上面的是,最后剩下的数即n=1时要删除的数, n = 1时, f(n, m ) = 0由此我们知道, f2 m

代码如下:

int JosephusProblem_Solution4(int n, int m)
{
	if(n < 1 || m < 1)
		return -1;

	vector<int> f(n+1,0);
	for(unsigned i = 2; i <= n; i++)
		f[i] = (f[i-1] + m) % i;

	return f[n];
}

有错请指出, 分享请标明出处! 谢谢


菜鸟系列之C/C++经典试题(五)