约瑟夫环问题

约瑟夫环问题

问题描述:

n个人围成一个圆圈依次编号为0,1,…,n-1,从第一个人开始,依次报数1, 2,…,k-1,报到k的人退出,从退出的下一个人开始继续从1报数,仍然报到k的人退出,问最后留下的人是谁。

朴素解法:

这是一个c语言入门题,我们可以用数组或者链表来模拟整个过程得到最终结果。但是这样复杂度为O(n^2),当n较大时没法算出结果。

数学解法:

先来模拟一下过程,当n = 8,k = 3时

  1. n = 8, k = 3

    0 1 2 3 4 5 6 7
    第一次编号为2的退出

  2. n = 7, k = 3

    0 1 3 4 5 6 7
    这次将从3开始数,从新进行编号
    3 4 5 6 7 0 1 (A)
    0 1 2 3 4 5 6 (B)
    可以发现从(B)式到(A)式有(A) = ((B) + k)%(n+1)

可以继续模拟下去,可以发现这个公式始终满足,显然我们知道当n==1的时候,最后留下的肯定为0,所以我们可以根据这个公式自底向上的计算下去了。

代码:

递归版本

int fun(int n, int k) {
    if(n == 1) return 0
    else return (fun(n - 1, k) + k) % n;
}

循环版本

const int maxn = 1000;
int f[maxn];
int fun(int n, int k) {
    f[0] = 1;
    for(int i = 0; i < n; i++) {
        f[i] = (f[i-1] + k) % n;
    }
}

很明显当有多次查询的时候,使用循环版本把答案一次算完可以避免重复计算,当然递归版本记忆化也是可以的。

拓展:

当然朴素的约瑟夫环肯定是一个送分题,这周bc就有一个简单拓展题目
King’s Game
为了铭记历史,国王准备在阅兵的间隙玩约瑟夫游戏。它召来了 n(1\le n\le 5000)n(1≤n≤5000) 个士兵,逆时针围成一个圈,依次标号 1, 2, 3 ... n1,2,3...n。
第一轮第一个人从 11 开始报数,报到 11 就停止且报到 11 的这个人出局。
第二轮从上一轮出局的人的下一个人开始从 11 报数,报到 22 就停止且报到 22 的这个人出局。
第三轮从上一轮出局的人的下一个人开始从 11 报数,报到 33 就停止且报到 33 的这个人出局。
第 n - 1n−1 轮从上一轮出局的人的下一个人开始从 11 报数,报到 n - 1n−1 就停止且报到 n - 1n−1 的这个人出局。
最后剩余的人是幸存者,请问这个人的标号是多少?

如果理解朴素的约瑟夫环话,可以发现这里就是一个简单的递推变化,同样定义 f(n,k) 为有n个人时,第k个人退出剩下的人的编号, f(n,k)=(f(n1,k+1)+k)

int f(int n, int k) {
    if(n == 1) return 0;
    else return (f(n - 1, k + 1) + k) % n;
}

复杂度O(n)

你可能感兴趣的:(算法,约瑟夫环)