约瑟夫环问题 许多人站成一圈,给每个人从1开始编号(也可以从0开始,不重要),第一个人从1开始报数,淘汰报了数字m的人,从淘汰的人的下一个人从1开始报数,重复淘汰,返回最后剩下的那个人的编号;
第一种方法,用递归,一步杀一人,千里不留行,O(M * N);
第二种方法,对于环形链表a--->b--->c--->d,设m = 2,一开始的他们的编号为1--->2--->3--->4,
报数之后淘汰b,变成了a--->c--->d,从c开始报数,所以他们的编号为3--->1--->2,然后往下递归,
到最后的时候,存活结点的编号一定会变成1;
上图看到,进行了3次,最终c的编号变成了1,现在设想有一个函数f,它可以拿着最后时刻c结点的1以及每次要报的间隔数m和总人数,推出上一步的c结点编号,也就是2,然后再拿着c结点的2,推出上一步c结点的编号,也就是1,再往上推,到了第一步,推出c结点的初始编号为3,可得出最终未被淘汰的人就是编号3,如果说函数f为O(1),那调n - 1次函数f,就可以得到结果,也就是时间复杂度为o(N);
现在不用abcd表示人,用1234的数字来表示,这样就可以捋出编号和报数之间的关系,比如下图
抽象为函数图像就是一个取模函数 y = ((x - 1) % i) + 1;
设现在有i = 7个数,m = 3,
前:1 2 3 4 5 6 7
淘汰3
后:5 6 X 1 2 3 4
用淘汰后的编号去推淘汰前的编号,自变量是淘汰后,因变量是淘汰前,得到下图
再次抽象化,设要淘汰的那个人的编号为s,淘汰跳数m,那淘汰后,原本淘汰前编号为s + 1的人的编号变为了1,原本淘汰前编号为s + 2的人的编号变为了2,到最后,原本淘汰前编号为 i 的人的编号变为了 i - s,而原本淘汰前编号为 i - s + 1的人的编号则会变为1;
它的定义域限死在 [1, i - 1],而这个函数可以由函数y = ((x - 1) % i) + 1整体向左移动s位得到;
(再次说明:x为淘汰后编号,y为淘汰前编号)
解释:上图中第一根斜线与y轴交点为s,而函数y = ((x - 1) % i) + 1如果延长到原点,与y轴交点为原点,之间差了s位;
这样这个函数就可以写成y = ((x - 1 + s) % i) + 1,s又为在 i 这个循环链表中要淘汰的下标值,
所以s = (m - 1) % i + 1,那么整个函数就可以写成y = ((x + (m - 1) % i) % i) + 1,表达式推导完毕;
该函数时间复杂度O(1),循环调用i - 1次(i 为链表长度),总体时间复杂度o(n)
表达式化简:设(m - 1) = k * i + r,则 y = ((x + (k * i + r) % i) % i) + 1 = ((x + r % i) % i) + 1 = ((x + r) % i) + 1;[ 因为 r 一定小于 i ] 另外一个表达式 y = ((x + m - 1) % i ) + 1 = ((x + k * i + r) % i) + 1 = ((x + r) % i ) + 1,最终两个表达式推导结果相同,所以那个复杂的表达式可以最终化简为 y = ((x + m - 1) % i ) + 1 ;