大家好,我是 @负雪明烛。👈👈 点击关注,优质题解不间断。

题目大意

1823. 找出游戏的获胜者 - 图3个人围成一个圈,每次数 1823. 找出游戏的获胜者 - 图4个数,被数到的那个人出局。问,圆圈中剩下的最后的一个人的标号。

这是经典的「约瑟夫环」问题。

本文是我「负雪明烛」于 2020-03-31 创作的《这或许是你能找到的最详细约瑟夫环数学推导!》

由于文章是之前写的,因此下文中变量和本题目有些不对应,但不影响理解:

  • 下文中的 1823. 找出游戏的获胜者 - 图5就是题目中的 1823. 找出游戏的获胜者 - 图6
  • 下文中是从 1823. 找出游戏的获胜者 - 图7 编号的,题目是从 1823. 找出游戏的获胜者 - 图8编号的,因此代码里面最后的返回结果需要 1823. 找出游戏的获胜者 - 图9.

解题方法

问题描述

约瑟夫环问题是这样的:

1823. 找出游戏的获胜者 - 图101823. 找出游戏的获胜者 - 图11 个数字排成一个圆圈,从数字 0 开始,每次从这个圆圈里删除第 1823. 找出游戏的获胜者 - 图12 个数字。求出这个圆圈里剩下的最后一个数字。

例如,1823. 找出游戏的获胜者 - 图13 这 5 个数字组成一个圆圈,从数字 0 开始每次删除第 3 个数字,则删除的前 4 个数字依次是 1823. 找出游戏的获胜者 - 图14,因此最后剩下的数字是 1823. 找出游戏的获胜者 - 图15。如下图所示。

1823. 找出游戏的获胜者 - 图16

解法

解决约瑟夫环问题,我们采用倒推,我们倒推出:最后剩下的这个数字,在最开始的数组中的位置。

  1. 剩下最后一个数字(简称“它”)的时候,总个数为 1,它的位置 1823. 找出游戏的获胜者 - 图17
  2. 那么它在上一轮也是安全的,总个数为 2,它的位置 1823. 找出游戏的获胜者 - 图18%20%5C%25%202#card=math&code=pos%20%3D%20%280%20%2B%20m%29%20%5C%25%202&id=KvPPt);
  3. 那么它在上上轮也是安全的,总个数为 3,它的位置 1823. 找出游戏的获胜者 - 图19%20%5C%25%202%20%2B%20m)%20%5C%25%203#card=math&code=pos%20%3D%20%28%280%20%2B%20m%29%20%5C%25%202%20%2B%20m%29%20%5C%25%203&id=eoVwm);
  4. 那么它在上上上轮也是安全的,总个数为 4,它的位置 1823. 找出游戏的获胜者 - 图20%20%5C%25%202%20%2B%20m)%20%5C%25%203)%20%5C%25%204#card=math&code=pos%20%3D%20%28%28%280%20%2B%20m%29%20%5C%25%202%20%2B%20m%29%20%5C%25%203%29%20%5C%25%204&id=yOkXN);
  5. 那么它在游戏开始的第一轮也是安全的,总个数为 1823. 找出游戏的获胜者 - 图21,它的位置 1823. 找出游戏的获胜者 - 图22 就是最后的结果。

即如果从下向上反推的时候:假如它前一轮的索引为 1823. 找出游戏的获胜者 - 图23,那么当前轮次的位置就是 1823. 找出游戏的获胜者 - 图24%20%5C%25#card=math&code=%28pos%20%2B%20m%29%20%5C%25&id=atyQf) 当前轮次的人数。

最后,由于给出的数字是 1823. 找出游戏的获胜者 - 图25,即 1823. 找出游戏的获胜者 - 图26,因此找出 1823. 找出游戏的获胜者 - 图27 就相当于找到这个数字。

大部分解法解到这里就结束了,缺乏递推公式的数学证明,现就数学推导说明如下。

数学推导

定义:

  1. 约瑟夫环操作:把一些数字排成一个圆圈,从数字 0 开始,每次从这个圆圈里删除第 1823. 找出游戏的获胜者 - 图28 个数字,直到最后只剩一个数字。
  2. 函数 1823. 找出游戏的获胜者 - 图29#card=math&code=f%28n%2C%20m%29&id=uaJug) :表示对 1823. 找出游戏的获胜者 - 图30 个数字 1823. 找出游戏的获胜者 - 图31 做约瑟夫环操作,最后剩下的这个数字。(这个定义特别重要,理解之后才向下看)

下面开始推导。

整体思路:

  • 在以 0 为起始的长度为 1823. 找出游戏的获胜者 - 图32 的序列上做约瑟夫环操作的最终结果 1823. 找出游戏的获胜者 - 图33%3D#card=math&code=f%28n%2C%20m%29%3D&id=CNXlL)
    在完成上轮操作删除数字 1823. 找出游戏的获胜者 - 图34 之后的新序列上的约瑟夫环操作的最终结果 1823. 找出游戏的获胜者 - 图35%3D#card=math&code=h%28n%20-%201%2C%20m%29%3D&id=cDzT3)
    将新序列映射成以 0 为起始的长度为 1823. 找出游戏的获胜者 - 图36 的序列上的约瑟夫环操作的最终结果 1823. 找出游戏的获胜者 - 图37#card=math&code=f%28n%20-%201%2C%20m%29&id=ClfYn) 的逆映射。
  • 得到下次操作的新序列

1823. 找出游戏的获胜者 - 图381823. 找出游戏的获胜者 - 图39 个数字中,第一个被删除的数字是 1823. 找出游戏的获胜者 - 图40%20%5C%25%20n#card=math&code=%28m%20-%201%29%20%5C%25%20n&id=gij9x)。为了简单起见,我们把 1823. 找出游戏的获胜者 - 图41%20%5C%25%20n#card=math&code=%28m%20-%201%29%20%5C%25%20n&id=kG71s) 记为 1823. 找出游戏的获胜者 - 图42,那么删除 1823. 找出游戏的获胜者 - 图43 之后剩下的 1823. 找出游戏的获胜者 - 图44 个数字为 1823. 找出游戏的获胜者 - 图45,并且下一次删除时要从 1823. 找出游戏的获胜者 - 图46 开始计数。相当于在剩下的序列中, 1823. 找出游戏的获胜者 - 图47 排在最前面,所以第二次操作的序列是 1823. 找出游戏的获胜者 - 图48

  1. 得到新序列上函数

在这个新序列上再完成约瑟夫环操作,最后剩下的数字应该是关于 1823. 找出游戏的获胜者 - 图491823. 找出游戏的获胜者 - 图50 的函数,即也可以用 1823. 找出游戏的获胜者 - 图51#card=math&code=f%28n%2C%20m%29&id=ZUuPn) 进行表示。但由于现在的这个序列的排列(从 1823. 找出游戏的获胜者 - 图52 开始)和最初的序列(从 0 开始)不一样,因此这个时候的函数已经不同于最初的函数,记为 1823. 找出游戏的获胜者 - 图53#card=math&code=h%28n%20-%201%2C%20m%29&id=LMoeS),此函数的定义:在 1823. 找出游戏的获胜者 - 图541823. 找出游戏的获胜者 - 图55个数字的序列上做约瑟夫环操作,最后剩下的这个数字

  1. 求解新函数

由于 在最初序列上 和 在新序列上 完成约瑟夫操作剩下的数字均为同一个数字,所以有 1823. 找出游戏的获胜者 - 图56%20%3D%20h(n%20-%201%2C%20m)#card=math&code=f%28n%2C%20m%29%20%3D%20h%28n%20-%201%2C%20m%29&id=QerQu)。(新序列是从最初序列转化的,会最终转化到同一个数字)

下面的工作就是求解新函数 1823. 找出游戏的获胜者 - 图57#card=math&code=h%28n%20-%201%2C%20m%29&id=Z9AWO) ,使其能够用 1823. 找出游戏的获胜者 - 图58#card=math&code=f%28n%20-%201%2C%20m%29&id=SBZVx) 表示出来。

由于 1823. 找出游戏的获胜者 - 图59#card=math&code=f%28n%20-%201%2C%20m%29&id=SQAvI) 是定义在以 0 为开始的序列上的,所以我们把剩下的这 1823. 找出游戏的获胜者 - 图60 个数字的序列 1823. 找出游戏的获胜者 - 图61 进行映射,映射到结果是形成一个 1823. 找出游戏的获胜者 - 图62 的序列。

  • 1823. 找出游戏的获胜者 - 图63
    1823. 找出游戏的获胜者 - 图64

    1823. 找出游戏的获胜者 - 图65
    1823. 找出游戏的获胜者 - 图66
    1823. 找出游戏的获胜者 - 图67

    1823. 找出游戏的获胜者 - 图68

该映射函数是个一元一次函数,定义为 1823. 找出游戏的获胜者 - 图69#card=math&code=p%28x%29&id=wEgH2),则 1823. 找出游戏的获胜者 - 图70%20%3D%20(x%20%2B%20n%20-%20k%20-%201)%20%5C%25%20n#card=math&code=p%28x%29%20%3D%20%28x%20%2B%20n%20-%20k%20-%201%29%20%5C%25%20n&id=yfaNq)。(还记得初中的 1823. 找出游戏的获胜者 - 图71 怎么求么?如果不懂见附录1)

从左到右的映射是 1823. 找出游戏的获胜者 - 图72#card=math&code=p%28x%29&id=c8A3Q),从右到左的映射叫做逆映射 1823. 找出游戏的获胜者 - 图73%20%3D%20(x%20%2B%20k%20%2B%201)%20%5C%25%20n#card=math&code=p%5E%7B-1%7D%28%20x%29%20%3D%20%28x%20%2B%20k%20%2B%201%29%20%5C%25%20n&id=ak3kd)。(该逆映射的求法见本章结尾附录2)

由于映射之后的序列和最初的序列有同样的形式,即都是从 0 开始的连续序列,因此在映射之后的序列上做约瑟夫环操作的结果仍可以用函数 1823. 找出游戏的获胜者 - 图74 表示,记为 1823. 找出游戏的获胜者 - 图75#card=math&code=f%28n%20-%201%2C%20m%29&id=XrErA)。

在映射之前的序列上的约瑟夫环操作的结果是 1823. 找出游戏的获胜者 - 图76#card=math&code=h%28n%20-%201%2C%20m%29&id=hZwlg),在映射之后的序列上的约瑟夫环操作的结果是 1823. 找出游戏的获胜者 - 图77#card=math&code=f%28n%20-%201%2C%20m%29&id=o0spK),则 1823. 找出游戏的获胜者 - 图78%20%3D%20p(%20h(n%20-%201%2C%20m)%20)#card=math&code=f%28n%20-%201%2C%20m%29%20%3D%20p%28%20h%28n%20-%201%2C%20m%29%20%29&id=QTPVm)。

所以有:

1823. 找出游戏的获胜者 - 图79%20%3D%20p%5E%7B-1%7D(x)(%20f(n%20-%201%2C%20m)%20)%20%3D%20%5B%20f(n%20-%201%2C%20m)%20%2B%20k%20%2B%201%5D%20%5C%25%20n%0A#card=math&code=h%28n%20-%201%2C%20m%29%20%3D%20p%5E%7B-1%7D%28x%29%28%20f%28n%20-%201%2C%20m%29%20%29%20%3D%20%5B%20f%28n%20-%201%2C%20m%29%20%2B%20k%20%2B%201%5D%20%5C%25%20n%0A&id=H5bKh)

1823. 找出游戏的获胜者 - 图80%20%5C%25%20n#card=math&code=k%20%3D%20%28m%20-%201%29%20%5C%25%20n&id=V1qVB) 代入得到:(1823. 找出游戏的获胜者 - 图81 中有取余运算,为什么代入之后没有了?见本章结尾附录3)

1823. 找出游戏的获胜者 - 图82%20%3D%20h(n%20-%201%2C%20m)%20%3D%20%5B%20f(n%20-%201%2C%20m)%20%2B%20m%20%5D%20%5C%25%20n%0A#card=math&code=f%28n%2C%20m%29%20%3D%20h%28n%20-%201%2C%20m%29%20%3D%20%5B%20f%28n%20-%201%2C%20m%29%20%2B%20m%20%5D%20%5C%25%20n%0A&id=g0KYf)

终于,经过复杂的分析,我们找到了一个只包含有 1823. 找出游戏的获胜者 - 图83 函数的递推公式。要得到 1823. 找出游戏的获胜者 - 图84 个数字的序列中完成约瑟夫环操作最后剩下的数字,只需要得到 1823. 找出游戏的获胜者 - 图85 个数字的序列中最后剩下的数字,并以此类推。(像不像递归?)

1823. 找出游戏的获胜者 - 图86 时,也就是序列中只有 1 个数字 0,那么完成约瑟夫操作最后剩下的数字就是0。(像不像递归终止条件?)

我们把这种关系表示为:

1823. 找出游戏的获胜者 - 图87%3D%0A%5Cbegin%7Bcases%7D%0A0%20%26%20n%20%3D%201%5C%5C%0A%5Cleft%5Bf(n%20-%201%2C%20m)%20%2B%20m%5Cright%5D%5C%25%20%20n%20%26%20%20n%3E1%0A%5Cend%7Bcases%7D%0A#card=math&code=f%28x%29%3D%0A%5Cbegin%7Bcases%7D%0A0%20%26%20n%20%3D%201%5C%5C%0A%5Cleft%5Bf%28n%20-%201%2C%20m%29%20%2B%20m%5Cright%5D%5C%25%20%20n%20%26%20%20n%3E1%0A%5Cend%7Bcases%7D%0A&id=W31U8)

这个公式无论是递归还是循环都很好实现。

附录1. 1823. 找出游戏的获胜者 - 图88#card=math&code=p%28x%29&id=dyXSc)的推导

看到这里的同学肯定好奇公式中的%号是怎么得到的,其实是个分段函数归纳的。

1823. 找出游戏的获胜者 - 图89%3D%0A%5Cbegin%7Bcases%7D%0Ax%20-%20k%20-%201%20%26%20k%20%2B%201%20%5Cleq%20x%20%5Cleq%20n%20-%201%5C%5C%0Ax%20%2B%20n%20-%20k%20-%201%20%26%200%20%5Cleq%20x%20%5Cleq%20k%20-%201%0A%5Cend%7Bcases%7D%0A#card=math&code=p%28x%29%3D%0A%5Cbegin%7Bcases%7D%0Ax%20-%20k%20-%201%20%26%20k%20%2B%201%20%5Cleq%20x%20%5Cleq%20n%20-%201%5C%5C%0Ax%20%2B%20n%20-%20k%20-%201%20%26%200%20%5Cleq%20x%20%5Cleq%20k%20-%201%0A%5Cend%7Bcases%7D%0A&id=f1Jip)

所以,为了把分段函数统一,使用 1823. 找出游戏的获胜者 - 图90%20%3D%20(x%20%2B%20n%20-%20k%20-%201)%20%5C%25%20n#card=math&code=p%28x%29%20%3D%20%28x%20%2B%20n%20-%20k%20-%201%29%20%5C%25%20n&id=B4bsd)。

附录2. 1823. 找出游戏的获胜者 - 图91#card=math&code=p%5E%7B-1%7D%28x%29&id=LqxSR)的推导

已知1823. 找出游戏的获胜者 - 图92%20%3D%20(x%20%2B%20n%20-%20k%20-%201)%20%5C%25%20n#card=math&code=p%28x%29%20%3D%20%28x%20%2B%20n%20-%20k%20-%201%29%20%5C%25%20n&id=ov2fv),求 1823. 找出游戏的获胜者 - 图93#card=math&code=p%5E%7B-1%7D%28x%29&id=P6MFw)。

1823. 找出游戏的获胜者 - 图94%20%3D%20(x%20%2B%20n%20-%20k%20-%201)%20%5C%25%20n%20%3D%20(x%20%2B%20n%20-%20k%20-%201)%20%2B%20(T%20-%201)n%20%3D%20x%20-%20k%20-%201%20%2B%20Tn%0A#card=math&code=p%28x%29%20%3D%20%28x%20%2B%20n%20-%20k%20-%201%29%20%5C%25%20n%20%3D%20%28x%20%2B%20n%20-%20k%20-%201%29%20%2B%20%28T%20-%201%29n%20%3D%20x%20-%20k%20-%201%20%2B%20Tn%0A&id=eDVyA)

其中引入的正整数 T 的取值方法:取合适的 T 以保证1823. 找出游戏的获胜者 - 图95%20%3C%3D%20n#card=math&code=0%20%3C%3D%20p%28x%29%20%3C%3D%20n&id=bUaXD)。(这一步不懂的可以用 1823. 找出游戏的获胜者 - 图96%20%3D%20n%20-%20k%20-1#card=math&code=p%280%29%20%3D%20n%20-%20k%20-1&id=e1nFy) 代入,此时 1823. 找出游戏的获胜者 - 图97

可以得到1823. 找出游戏的获胜者 - 图98%20%2B%20k%20%2B%201%20-%20Tn#card=math&code=x%20%3D%20p%28x%29%20%2B%20k%20%2B%201%20-%20Tn&id=fFj6p),逆函数就是把 x 替换成 1823. 找出游戏的获胜者 - 图99#card=math&code=p%28x%29&id=Vu9vy),把 1823. 找出游戏的获胜者 - 图100#card=math&code=p%28x%29&id=or2ZV) 替换成 x。

所以逆函数 1823. 找出游戏的获胜者 - 图101%20%3D%20x%20%2B%20k%20%2B%201%20-%20Tn%20%3D%20(x%20%2B%20k%20%2B%201)%20%5C%25%20n#card=math&code=p%5E%7B-1%7D%28x%29%20%3D%20x%20%2B%20k%20%2B%201%20-%20Tn%20%3D%20%28x%20%2B%20k%20%2B%201%29%20%5C%25%20n&id=EIdQZ)。

附录3. 消除括号里的模运算

模运算的四则规则:1823. 找出游戏的获胜者 - 图102%20%5C%25%20p%20%3D%20(a%20%5C%25%20p%20%2B%20b%20%5C%25%20p)%20%5C%25%20p#card=math&code=%28a%20%2B%20b%29%20%5C%25%20p%20%3D%20%28a%20%5C%25%20p%20%2B%20b%20%5C%25%20p%29%20%5C%25%20p&id=MRjxF),选自百度百科

证明 1823. 找出游戏的获胜者 - 图103%20%2B%20%20(m%20-%201)%20%5C%25%20n%20%2B%201%5D%20%5C%25%20n%20%3D%20%5B%20f(n%20-%201%2C%20m)%20%2B%20m%20%5D%20%5C%25%20n#card=math&code=%5B%20f%28n%20-%201%2C%20m%29%20%2B%20%20%28m%20-%201%29%20%5C%25%20n%20%2B%201%5D%20%5C%25%20n%20%3D%20%5B%20f%28n%20-%201%2C%20m%29%20%2B%20m%20%5D%20%5C%25%20n&id=FMlcv) .

左边 = 1823. 找出游戏的获胜者 - 图104%20%2B%20%20(m%20-%201)%20%5C%25%20n%20%2B%201%5D%20%5C%25%20n#card=math&code=%5B%20f%28n%20-%201%2C%20m%29%20%2B%20%20%28m%20-%201%29%20%5C%25%20n%20%2B%201%5D%20%5C%25%20n&id=a13g1)

  • = 1823. 找出游戏的获胜者 - 图105%20%2B%201)%20%5C%25%20n%20%2B%20%20%20(m%20-%201)%20%5C%25%20n%5D%20%5C%25%20n#card=math&code=%5B%20%28f%28n%20-%201%2C%20m%29%20%2B%201%29%20%5C%25%20n%20%2B%20%20%20%28m%20-%201%29%20%5C%25%20n%5D%20%5C%25%20n&id=RsHu1) ,由于1823. 找出游戏的获胜者 - 图106%20%3C%20n%20-%202#card=math&code=0%20%3C%20%E6%98%A0%E5%B0%84%E5%90%8E%E7%9A%84%E5%8F%96%E5%80%BC%20f%28n%20-%201%2C%20m%29%20%3C%20n%20-%202&id=sAKZJ),所以1823. 找出游戏的获胜者 - 图107%20%2B%201%3C%20n%20-%201#card=math&code=1%20%3C%20f%28n%20-%201%2C%20m%29%20%2B%201%3C%20n%20-%201&id=AqGFg),所以可以添加第一个取余运算。
    = 1823. 找出游戏的获胜者 - 图108%20%2B%201)%20%20%2B%20%20%20(m%20-%201)%20%5D%20%5C%25%20n#card=math&code=%5B%20%28f%28n%20-%201%2C%20m%29%20%2B%201%29%20%20%2B%20%20%20%28m%20-%201%29%20%5D%20%5C%25%20n&id=AxHOd) ,运用四则运算公式
    = 1823. 找出游戏的获胜者 - 图109%20%20%2B%20%20m%20%5D%20%5C%25%20n#card=math&code=%5B%20f%28n%20-%201%2C%20m%29%20%20%2B%20%20m%20%5D%20%5C%25%20n&id=pe1yg)
    = 右边

得证。

代码

1823. 找出游戏的获胜者 - 图110 开始,代表了最后结果只剩下了 1 个数字,这个数字处于第 0 个位置。

循环从数组长度有 2 个开始,即从剩下了两个数字开始计算。

循环到数组中剩下 1823. 找出游戏的获胜者 - 图111 个人结束,即到达了题目要求的那么多数字,此时的 1823. 找出游戏的获胜者 - 图112 就是最后剩下的那个数字的在 1823. 找出游戏的获胜者 - 图113 个数字中位置。

Python 代码如下:

  1. class Solution(object):
  2. def findTheWinner(self, n, k):
  3. pos = 0
  4. for i in range(2, n + 1):
  5. pos = (pos + k) % i
  6. return pos + 1

复杂度

  • 时间复杂度:$O(N)$
  • 空间复杂度:$O(1)$

参考文献

  1. 《剑指Offer》,本数学推导基于《剑指Offer》中的推导进行了更详细的讲解。
  2. 力扣(LeetCode)链接:https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof
  3. https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/solution/javajie-jue-yue-se-fu-huan-wen-ti-gao-su-ni-wei-sh/

总结

  1. 本文的证明非常、非常详细,实际不难,有些笔试题中可能会考这个代码。

我是 @负雪明烛 ,刷算法题 1000 多道,写了 1000 多篇算法题解,收获阅读量 300 万。
关注我,你将不会错过我的精彩动画题解、面试题分享、组队刷题活动,进入主页 @负雪明烛 右侧有刷题组织,从此刷题不再孤单。