约瑟夫经典问题:N个人围成一圈,第一个人从1开始报数,报M的将被杀掉,下一个人接着从1开始报。如此反复,最后剩下一个,求最后的胜利者。题目可见:找出游戏的胜利者
首先考虑常规解法:队列模拟。用一个队列模拟环,报到M的数字移除队列,其余的数字添加到队列末尾,直到队列中的数字个数只有一个为止,时间复杂度是O(nm),每m次离开一个,一共进行n次,空间复杂度是O(n)。代码如下:
int findTheWinner(int n, int k) {
k%=n; //取余
if(k==0) k=n;;
queue<int>q;
for(int i=1;i<=n;i++) q.push(i);
while(q.size()!=1){
int t=k-1;
while(t){
q.push(q.front());
q.pop();
t--;
}
q.pop(); //每轮离开一个
}
return q.front();
}
可以看到,即使采用了取余操作,时间复杂度最坏的情况也是O(n^2)的,因此我们可以采用其他的方法。
- 假设用 f ( n , k ) f(n,k) f(n,k)表示n个人围成一个圈,每轮报数为k的人离开圈子时的获胜者编号
- 当 n = 1 n=1 n=1时,由于圈子中只有一个人,因此他就是获胜者,即 f ( 1 , k ) = 1 f(1,k)=1 f(1,k)=1
- 当 n > 1 n>1 n>1时,在第一轮将会离开一个人,剩下n-1个人,假设离开的是k’,满足 1 ≤ k ′ ≤ n 1≤k'≤n 1≤k′≤n,且 k − k ′ k-k' k−k′是n的倍数
- 由于 1 ≤ k ′ ≤ n 1≤k'≤n 1≤k′≤n,因此 0 ≤ k ′ − 1 ≤ n − 1 0≤k'-1≤n-1 0≤k′−1≤n−1,又由于 k − k ′ k-k' k−k′是 n n n的倍数,因此 ( k − 1 ) − ( k ′ − 1 ) (k-1)-(k'-1) (k−1)−(k′−1)是 n n n的倍数,因此 k ′ − 1 = ( k − 1 ) m o d k'-1=(k-1) mod k′−1=(k−1)mod n n n, k ’ = ( k − 1 ) m o d k’=(k-1) mod k’=(k−1)mod n + 1 n+1 n+1
- 令 x = f ( n − 1 , k ) x=f(n-1,k) x=f(n−1,k),当第 k ′ k' k′离开圈子后,获胜者的编号是从 k ′ + 1 k'+1 k′+1开始的第 x x x个人的编号,因此 f ( n , k ) = ( k ′ m o d f(n,k)=(k' mod f(n,k)=(k′mod n + x − 1 ) m o d n +x-1) mod n+x−1)mod n + 1 = ( k + x − 1 ) m o d n +1=(k+x-1)mod n+1=(k+x−1)mod n + 1 n + 1 n+1
- 将 x = f ( n − 1 , k ) x=f(n-1,k) x=f(n−1,k)带入上述关系,可得: f ( n , k ) = ( k + f ( n − 1 , k ) − 1 ) m o d f(n,k)=(k+f(n-1,k)-1)mod f(n,k)=(k+f(n−1,k)−1)mod n + 1 n+1 n+1
递归:
int findTheWinner(int n, int k) {
if (n == 1) {
return 1;
}
return (k + findTheWinner(n - 1, k) - 1) % n + 1;
}
迭代:
int findTheWinner(int n, int k) {
int winner = 1;
for (int i = 2; i <= n; i++) {
winner = (k + winner - 1) % i + 1;
}
return winner;
}