微软面试题之一,难度系数中
题目描述:
题目:n 个数字(0,1,…,n-1)形成一个圆圈,从数字0 开始,
每次从这个圆圈中删除第m 个数字(第一个为当前数字本身,第二个为当前数字的下一个
数字)。
当一个数字删除后,从被删除数字的下一个继续删除第m 个数字。
求出在这个圆圈中剩下的最后一个数字。
逻辑分析:
前言:赫赫有名的约瑟夫环,接触过算法的朋友想必耳熟能详(如果你学习了算法课程依旧不了解,思过崖3日游不谢),早年常见于大公司面试,且上机编程,尽管常见,难度系数还是有的。
1、一般来说,各大公司都是要求用链表来实现,尽管顺序表显然方便的多。思路也很简单,n个数字,一共需要删除n-1个,而每一次都要遍历m个元素,那么如果用计算机轮询,也就是模拟删除的方法(类似枚举),时间复杂度为O(m*(n-1)),显然是平方级的,效率虽然低,不过也是常规解法。这里提供一份代码。
#include <iostream.h>
#include <malloc.h>
typedef struct LNode
{
int data;
struct LNode *next;
}*LinkList;
void CreateList(LinkList &L)
{
L=(LinkList)malloc(sizeof(LNode));
L->data=0;
L->next=NULL;
LNode *p=L;
cout<<"请输入数字的个数N:"<<endl;
int n,i;
cin>>n;
LNode *q;
for(i=0;i<n;i++)
{
q=(LinkList)malloc(sizeof(LNode));
q->data=i;
p->next=q;
q->next=NULL;
p=q;
}
q->next=L->next;
}
void Joesf(LinkList L,int m)
{
LinkList p,q;
p=L;
int n;
while(L->next->next!=L->next)
{
q=p->next;
n=m;
while(n>1)
{
q=q->next;
p=p->next;
n--;
}
if(q==L->next)
{
LNode *k=q;
while(k->next!=q)
k=k->next;
L->next=q->next;
k->next=q->next;
//cout<<q->data<<endl;
}
else
{
p->next=q->next;
//cout<<q->data<<endl;
q=q->next;
}
}
cout<<"最后一个元素是:"<<L->next->data<<endl;
}
void main()
{
LinkList L;
CreateList(L);
int m;
cout<<"请输入m:"<<endl;
cin>>m;
Joesf(L,m);
}
2、上述迷你过程效率低下,是因为每次找到要删除的数都要进行m次的查找。那么如何减少查找次数?这里就要借助数学来“化简过程”,通过计算删除的元素,比如第一次删除,(m-1)%n的结果即为要删除的数。删除以后,则首个元素变为(m-1)%n+1,为了方便,我们将(m-1)%n记为k。
3、重点在于如何找到删除前和删除后,各元素的位置关系,图示:
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)之间的关系?
通过上表可以得到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
那么,代码也就呼之欲出了。
int LastRemaining(unsigned int n, unsigned int m)
{
if(n < 1 || m < 1)
return -1;
int last = 0;
for (int i = 2; i <= n; i ++)
last = (last + m) % i;
return last;
}
显然,时间复杂度O(n)。