约瑟夫环问题

一、介绍

约瑟夫环(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];
    }
};
相关推荐
佳児素花痴╮28 分钟前
树的基础知识与查找排序算法
数据结构·算法
lueluelue477 小时前
LeetCode:链表
算法·leetcode·链表
橘子汽水16810 小时前
Leetcode 23,543合并K个升序链表,二叉树的直径
算法·leetcode·链表
huameinan狮子10 小时前
Adaboost算法原理与计算实例
算法
别怪我很水10 小时前
API中提供了VelocityTracker类用于计算触摸事件MotionEvent的速度,而其内部默认使用的方法就是最小二乘法,本 ...
算法·gitee·最小二乘法
Re.不晚13 小时前
挑战做100道力扣算法- DAY1
算法·leetcode·职场和发展
蓝悦无人机14 小时前
《Planning algorithms》读书笔记——第1章 引言
算法·读书笔记·规划算法·lavalle
Sagittarius_A*14 小时前
分组密码基础(二):Feistel 结构与 DES 的设计思想
算法·信息安全·密码学·des·数论
青山木14 小时前
Hot 100 --- 搜索插入位置
java·数据结构·算法·leetcode