最近一段时间一直在找工作,没有心思写心得。今天刚好从淳中视讯面试回来,有一道题很有意思。
1. n个人围成一个圆圈,从1到3开始报数,报到3的人出去,这样一直循环,直到只剩下一个人。问剩下的人在开始报数之前的编号?
当时我想的是直接用循环链表来模拟整个报数的过程,即构造一个n个数的循环链表,进行循环遍历,每当数到3时删除当前指向的节点,直到剩下最后一个。
但是该算法比较复杂,肯定还有简单的算法。
回来查了一下百度果然是这样,这是经典的约瑟夫问题:
据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
我们回到解题思路上,考虑一般的情况,n个人排成一圈,从第1个人开始报数,报数口号从1到m(报到m的人从圆圈里出去),循环直到只剩一个人,问最后剩下的人开始的编号是多少?
假设答案ans = F(n,m)。则F(1,m)=1; F(2,2) = 1; F(2,3) = 2;
假设有n个数: 1,2,3,4,5,6....,n-2,n-1,n。
中奖的第一个人的序号一定是:a1 = (m%n ).那么剩下的人构成一个n-1约瑟夫环:
1,2,3...a1-1,a1+1...n-1,n.
即: a1+1,a1+2....1,2,...a1-1. 每个元素做映射:
a1+1 -> 1,
a1+2 -> 2,
.....
a1-1 -> n-1.
剩下的数构成一个新的约瑟夫环并且规模为n-1.
因为从n转换到n-1的时候,每个元素都进行了减少a1的循环映射,而且a1 =m%n
所以有递推公式:F(n,m) = ( F(n-1,m) + (m%n) -1 )%n + 1.
且F(1,m)=1;
考虑更一般的情况:起始报数从第k个人开始:
则先将每个元素进行减少k-1的循环映射:
k -> 1
k+1 -> 2
....
k-1 -> n
则F(n,m,k) = (F(n,m) + k-1 -1) % n +1.
有了这些公式后就可以编程实现啦:
输入三个数,总数n,报数口号m,从第k个人开始报数。
输出是最后剩下的人开始的编号(注: 编号从1开始,报数从1开始)
int Func(int n,int m,int k)
{
assert(n>0 && m>1 && k>0); //断言输入在正确的范围内
if (n == 1) return 1;
int ans = 1;
int index = 1;
while (index < n)
{
index++;
ans = (ans + m%index -1) % index + 1;
}
ans = (ans + k-1 -1)%n + 1;
return ans;
}
时间复杂度是O(n).
本文介绍了在软件开发面试笔试中遇到的约瑟夫问题,即人们围成圈报数,报到特定数字的人出局,直到只剩一人。通过分析得出递推公式,并讨论了从不同位置开始报数的影响,提供了O(n)时间复杂度的解决方案。

715

被折叠的 条评论
为什么被折叠?



