约瑟夫环问题

一、介绍

约瑟夫环(Josephus Problem) 是一个著名的数学和计算机科学问题,大致意思是:N个人围成一圈,从第一个人开始报数,报到第M个人就将其淘汰出圈,然后从下一个人重新开始报数,如此循环,直到剩下最后一个人。求这个幸存者的初始位置。

题目链接:孩子们的游戏(圆圈中最后剩下的数)

二、解决方案

1. 模拟

该方法是最直观的,就是开辟一个数组记录哪些人被淘汰,一直遍历下去直至只剩下一个人。

cpp 复制代码
class Solution {
public:
    int LastRemaining_Solution(int n, int m) {
        vector<bool> check(n, true);

        int cnt = 0, idx = 0, alive = n;
        while(alive > 1)
        {
            if(check[idx])
            {
                cnt++;
                if(cnt == m)
                {
                    cnt = 0;
                    alive--;
                    check[idx] = false;
                }
            }
                
            if(++idx == n) idx = 0;
        }

        for(int i = 0; i < n; i++)
            if(check[i])
                return i;

        return -1;
    }
};

2. 递归

上述的方法确实可以解决问题,但是时间复杂度会有点高,下面介绍一下很巧妙的方法。它的核心是假设一共有 n 个人,在淘汰了一个之后就只剩下了 n - 1 个人,那么我们是不是可以将 n - 1 个人看作一个新的约瑟夫环问题。这个思想就是递归的核心!

上面我们在了解了思想后来想几个关键问题。当人数从 n 变成 n - 1 的时候,下一次淘汰人需要从这一次淘汰人的后一位开始计数,这时会发现好像下一个人的编号不一定是从 0 开始的呀!所以这里需要做一下映射。我们假设 k 是这一次淘汰的编号( k = (m - 1) % n ),那么下一次开始的位置就是 k + 1,它对应的新编号是 0、以此类推 k + 2 -> 1、k + 3 -> 2...。具体可以看下面 n=5, m=3 的例子:

黑色那一圈是初始 n 人旧编号,淘汰下标 k 之后,蓝色是剩下 n−1 人的新编号。所以可以推出:旧编号 = 新编号 + k + 1,但这对吗?很明显在上图中 k+3 已经超过原来的范围了所以实际是出:旧编号 = (新编号 + k + 1) % n 。这样看是不是就成了比原来更小一层的问题了。这里可以将 k = (m - 1) % n 公式合并到那个公式中,可以得到:旧编号 = (新编号 + m) % n

想一个边界情况,如果 n 等于 1 呢?这时其实就可以直接返回因为只有一个人,而幸存者编号就是 0。所以递推公式如下( f(x)表示人数为 x 时幸存者的编号):

递归实现:

cpp 复制代码
class Solution {
public:
    int LastRemaining_Solution(int n, int m) {
        if(n == 1)
            return 0;

        return (LastRemaining_Solution(n - 1, m) + m) % n;
    }
};

dp实现:

cpp 复制代码
class Solution {
public:
    int LastRemaining_Solution(int n, int m) {
        vector<int> dp(n + 1);
        for(int i = 2; i <= n; i++)
            dp[i] = (dp[i - 1] + m) % i;

        return dp[n];
    }
};
相关推荐
科技大视界3 小时前
投资AI项目,传统尽调不够用了——李章虎律师拆解算法、数据、算力三大雷区
人工智能·算法·数据挖掘
郝学胜-神的一滴3 小时前
算法实战:最小k个数——大顶堆的优雅解法
开发语言·数据结构·c++·python·程序人生·算法·排序算法
Irissgwe3 小时前
算法滑动窗口
数据结构·算法
怪兽学LLM3 小时前
LeetCode 105. 从前序与中序遍历序列构造二叉树:分治递归思路详解
算法·leetcode·职场和发展
可编程芯片开发4 小时前
基于PI控制的三相整流器控制系统的simulink建模与仿真,包含超级电容充电和电机
算法
酿情师4 小时前
区块链共识算法深度拆解:PoW、PoS、PBFT、Raft 原理解析
算法·区块链·共识算法
hansang_IR4 小时前
【记录】「SCOI2016」三道模拟赛/26.7.12
c++·算法
退休倒计时4 小时前
【每日一题】LeetCode 39. 组合总和 TypeScript
算法·leetcode·typescript
不是az4 小时前
力控相关知识点
算法·机器学习·最小二乘法