Backto Algo Index
N N N 个人围一圈, 选一个人, 从 1 开始计数, 计到第 k k k 个人出局, 剩下的人组成新环, 再次从 1 开始计数, 计到 k k k 的人出局. 如此反复, 直到剩下最后一个人, 作为胜利者. 问, 在游戏开始时, 排在第几号位置, 可以保证自己是最后的胜利者?
举一个特例理解题目, 设 N = 6 , k = 5 N = 6, k = 5 N=6,k=5, 则这一圈人是
1 | 2 | 3 | 4 | 5 | 6 | 1 | … |
---|
出局的顺序依次是 5
-> 4
-> 6
-> 2
-> 3
, 所以开局时候排在第 1 1 1 位是胜利者.
编程求解的直接思路: 用一个 循环链表 储存这 N N N 个人, data 里存一个 bool 值, true 表示还在场上, false 表示出局. 然后循环遍历 N − 1 N - 1 N−1次, 最后剩下的人就是胜利者. But, 时间复杂度是 N × k N \times k N×k, 任何一个数非常大的话, 这个复杂度都是不可接受的.
优化, 重新分析问题, 试图找到其中的隐含规律.
计算机语言问题描述: n n n 个人(编号 0 → ( n − 1 ) 0\to(n-1) 0→(n−1)),从 0 0 0开始报数,报到 ( m − 1 ) (m-1) (m−1)的退出,剩下的人继续从 0 0 0 开始报数。求胜利者的编号。
我们可以确定, 第一个出列的人编号肯定是 ( m − 1 ) m o d    n (m-1) \mod n (m−1)modn, 那么剩下的 n − 1 n-1 n−1 个人组成新的 约瑟夫环, 新环肯定以 ( k = m m o d    n k = m \mod n k=mmodn) 开始, 新环的排序肯定是 k , k + 1 , k + 2 , . . . , n − 2 , n − 1 , 0 , 1 , 2 , . . . , k − 2 k, k+1, k+2, ... , n-2, n-1, 0, 1, 2, ... , k - 2 k,k+1,k+2,...,n−2,n−1,0,1,2,...,k−2. 把这个新环的序号调整一下, 看做一个新的约瑟夫环
也就是说变成了 n − 1 n-1 n−1 个人报数的子问题. 假设我们知道对于这个 n − 1 n-1 n−1个人的子问题, 第 x x x 个人是最终的胜利者, 那么我们再把这个 x x x 塞回去就是刚好 n n n个人情况下的解了. 变回去的公式就是 x ′ = ( x + k ) m o d    n x' = (x+k) \mod n x′=(x+k)modn. 因此, 我们知道了 n − 1 n-1 n−1 下的解就可以求出 n n n 的解, 典型的递归问题.
const int n = 68; // total number
const int m = 3; // kill the m_th person
int f = 0; // safe position
int main()
{
for (int i = 1; i <= n; i++)
f = (f + m) % i;
cout << f + 1 << endl; // counts from 1
}
时间复杂度 O ( n ) O(n) O(n), 空间复杂度 O ( 1 ) O(1) O(1), 好很多了.