🚩传送门:牛客题目
力扣题目

题目

[NC]132. 约瑟夫问题 - 图1[NC]132. 约瑟夫问题 - 图2 个数字排成一个圆圈,从数字 [NC]132. 约瑟夫问题 - 图3 开始,每次从这个圆圈里删除第 [NC]132. 约瑟夫问题 - 图4 个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。

例如:0、1、2、3、45 个数字组成一个圆圈,从数字0开始每次删除第 3 个数字,则删除的前 4 个数字依次是2、0、4、1,因此最后剩下的数字是 **3**

进阶:空间复杂度 [NC]132. 约瑟夫问题 - 图5,时间复杂度 [NC]132. 约瑟夫问题 - 图6

示例 1:

输入: n = 5, m = 3 输出: 3

示例 2:

输入: n = 10, m = 17 输出: 2

解题思路:动态规划

本题是著名的 “约瑟夫环” 问题,可使用 动态规划 解决。

模拟法需要循环删除 [NC]132. 约瑟夫问题 - 图7 轮,每轮在链表中寻找删除节点需要 [NC]132. 约瑟夫问题 - 图8 次访问操作(链表线性遍历),因此总体时间复杂度为 [NC]132. 约瑟夫问题 - 图9 。题目给定的 [NC]132. 约瑟夫问题 - 图10 取值范围如下所示,观察可知此时间复杂度是不可接受的。

[NC]132. 约瑟夫问题 - 图11

输入 [NC]132. 约瑟夫问题 - 图12 ,记此约瑟夫环问题为 「[NC]132. 约瑟夫问题 - 图13 问题」 ,设解(即最后留下的数字)为 [NC]132. 约瑟夫问题 - 图14 ,则有:

  • [NC]132. 约瑟夫问题 - 图15 问题」:数字环为 [NC]132. 约瑟夫问题 - 图16 ,解为 [NC]132. 约瑟夫问题 - 图17
  • [NC]132. 约瑟夫问题 - 图18 问题」:数字环为 [NC]132. 约瑟夫问题 - 图19 ,解为 [NC]132. 约瑟夫问题 - 图20
  • 以此类推……

    请注意,数字环是 首尾相接 的,为方便行文,本文使用列表形式表示。

👉 定义:[NC]132. 约瑟夫问题 - 图21 表示约瑟夫环问题为 「[NC]132. 约瑟夫问题 - 图22 问题」 的最终剩余答案

对于 「[NC]132. 约瑟夫问题 - 图23 问题」 ,首轮删除环中第 [NC]132. 约瑟夫问题 - 图24 个数字后,能够得到一个长度为 [NC]132. 约瑟夫问题 - 图25新的数字环。🐛由于有可能 [NC]132. 约瑟夫问题 - 图26 ,因此删除的数字为 [NC]132. 约瑟夫问题 - 图27 ,删除后的数字环从下个数字(即 [NC]132. 约瑟夫问题 - 图28 )开始,设 [NC]132. 约瑟夫问题 - 图29 ,可以得到下列新的数字环

[NC]132. 约瑟夫问题 - 图30
image.png
删除一轮后的数字环也变为一个「[NC]132. 约瑟夫问题 - 图32 问题」,观察以下数字编号对应关系:

[NC]132. 约瑟夫问题 - 图33
👉 定义: [NC]132. 约瑟夫问题 - 图34 表示约瑟夫环问题为 「[NC]132. 约瑟夫问题 - 图35 问题」 的所有序列值 [NC]132. 约瑟夫问题 - 图36

对于「[NC]132. 约瑟夫问题 - 图37 问题」中的 [NC]132. 约瑟夫问题 - 图38 来说,其值范围 [NC]132. 约瑟夫问题 - 图39,对于「[NC]132. 约瑟夫问题 - 图40 问题」删除后中的 [NC]132. 约瑟夫问题 - 图41 来说,其值范围 [NC]132. 约瑟夫问题 - 图42,因此需要找到 [NC]132. 约瑟夫问题 - 图43[NC]132. 约瑟夫问题 - 图44 的关系 。设 [NC]132. 约瑟夫问题 - 图45[NC]132. 约瑟夫问题 - 图46 我们需要找出 [NC]132. 约瑟夫问题 - 图47映射关系

[NC]132. 约瑟夫问题 - 图48

🚩首先我们知道 [NC]132. 约瑟夫问题 - 图49 是一定相等的

  1. 首先在n个数序列中,删除第m个数字,会形成n-1个数序列
  2. 接着在n-1个数序列中,删除第m个数字,会形成n-2个数序列
  3. 接着在n-2个数序列中,删除第m个数字,会形成n-3个数序列
  4. 接着在2个数序列中,删除第m个数字,会形成1个数序列,这就是结果

image.png
因此,在 [NC]132. 约瑟夫问题 - 图51 个数字序列中不断删除第 [NC]132. 约瑟夫问题 - 图52 个数字,按序列 [NC]132. 约瑟夫问题 - 图53 最终保留的结果[NC]132. 约瑟夫问题 - 图54 ,则映射到序列 [NC]132. 约瑟夫问题 - 图55最终保留的结果[NC]132. 约瑟夫问题 - 图56

[NC]132. 约瑟夫问题 - 图57

备注:t=m%n由其定义得到的,可以看上面的🐛定义

最终发现了推导公式:

🚩 [NC]132. 约瑟夫问题 - 图58

👉 定义:[NC]132. 约瑟夫问题 - 图59 表示约瑟夫环问题为「[NC]132. 约瑟夫问题 - 图60 问题」的最终剩余答案

一共i个数,从下标0开始,每次删除第m个数字,最终剩余的唯一一个数就是dp[i]

边界条件: [NC]132. 约瑟夫问题 - 图61 , [NC]132. 约瑟夫问题 - 图62
🤣综上所述,我们不难发现,状态转移方程
[NC]132. 约瑟夫问题 - 图63

官方代码

  1. class Solution {
  2. public int lastRemaining(int n, int m) {
  3. int []dp = new int[n+1]; // 默认初始化为 0
  4. for (int i = 2; i <= n; i++) {
  5. dp[i] = (dp[i-1] + m) % i;
  6. }
  7. return dp[n];
  8. }
  9. }

最后我们发现,每次状态只与前一位的状态有关,因此我们可以进行空间优化,只保存前面的状态即可。

时间复杂度

时间复杂度:[NC]132. 约瑟夫问题 - 图64,其中 [NC]132. 约瑟夫问题 - 图65 为序列的长度,需要循环 [NC]132. 约瑟夫问题 - 图66 次。

空间复杂度:[NC]132. 约瑟夫问题 - 图67,需要额外使用长度为 [NC]132. 约瑟夫问题 - 图68[NC]132. 约瑟夫问题 - 图69 数组,但是可以使用空间优化。

🚩官方代码

  1. class Solution {
  2. public int lastRemaining(int n, int m) {
  3. int x = 0;
  4. for (int i = 2; i <= n; i++) {
  5. x = (x + m) % i;
  6. }
  7. return x;
  8. }
  9. }