原题链接
解题思路
模拟整个删除过程最直观,即构建一个长度为 的链表,各节点值为对应的顺序索引;每轮删除第 个节点,直至链表长度为 1 时结束,返回最后剩余节点的值即可。
模拟法需要循环删除 轮,每轮在链表中寻找删除节点需要 次访问操作(链表线性遍历),因此总体时间复杂度为 。题目给定的 取值范围如下所示,观察可知此时间复杂度是不可接受的。
实际上,本题是著名的 “约瑟夫环” 问题,可使用 动态规划 解决。
输入 ,记此约瑟夫环问题为 「 问题」 ,设解(即最后留下的数字)为 ,则有:
- 「 问题」:数字环为 ,解为 ;
- 「 问题」:数字环为 ,解为 ;
- 以此类推……
请注意,数字环是 首尾相接 的,为方便行文,本文使用列表形式表示。
对于「 问题」,首轮删除环中第 个数字后,得到一个长度为 的数字环。由于有可能 ,因此删除的数字为 ,删除后的数字环从下个数字(即 )开始, 设 ,可得数字环:
例如,当时,第一个被删除的数字是 ,下一个计数开始的数字是。
删除一轮后的数字环也变为一个「 问题」,观察以下数字编号对应关系:
设「 问题」某数字为 ,则可得递推关系(关键点):
如何理解上面的公式?
对于 问题」中的任意数字,我们都能推出它在「 问题」删除一个值后的对应值为(应该指的是下标,本题中刚好下标等于数字本身)
即两个问题可以对应起来,长度为 长度为 经过第一轮删除
换而言之,若已知「 问题」的解 (问题中剩下的最后的数字),则可通过以上公式计算得到「 问题」的解 (问题中剩下的最后的数字) ,即:
可由 得到, 可由 得到,……, 可由 得到;
因此,若给定 的值,就可以递推至任意 。而「 问题」的解 恒成立,即无论 为何值,长度为 1 的数字环留下的是一定是数字 。
以上数学推导本质是得出动态规划的 转移方程
和 初始状态
。
动态规划解析:
- 状态定义: 设「 问题」的解为
- 转移方程: 通过以下公式可从 递推得到
- 初始状态:「 问题」的解恒为 ,即
- 返回值: 返回「 问题」的解
复杂度分析
- 时间复杂度 : 状态转移循环 次使用 时间,状态转移方程计算使用 时间;
- 空间复杂度 : 使用常数大小的额外空间;
代码:
typescript
function lastRemaining(n: number, m: number): number {
const dp = [];
dp[1] = 0; // 初始条件
for(let i = 2; i <= n; i++) {
dp[i] = (dp[i - 1] + m) % i;
}
return dp[n];
};