题目:
0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,
每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。
求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,
则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
---------------------
示例 :
输入: n = 5, m = 3
输出: 3
输入: n = 10, m = 17
输出: 2
限制:
1 <= n <= 10^5
1 <= m <= 10^6
---------------------
思考:
模拟整个删除过程最直观,即构建一个长度为 n 的链表,各节点值为对应的顺序索引;
每轮删除第 m 个节点,直至链表长度为 1 时结束,返回最后剩余节点的值即可。
模拟法需要循环删除 n - 1轮,每轮在链表中寻找删除节点需要 m 次访问操作(链表线性遍历),
因此总体时间复杂度为 O(nm) 。题目给定的 m, n取值范围如下所示,
观察可知此时间复杂度是不可接受的。
1≤n≤10^5
1≤m≤10^6
----------------------
实际上,本题是著名的 “约瑟夫环” 问题,可使用 动态规划 解决。
以 n = 5 , m = 3的示例如下图所示。
如下图所示,为 n = 5 , m = 3 时的状态转移和对应的模拟删除过程。
复杂度分析:
时间复杂度 O(n): 状态转移循环 n - 1次使用 O(n)时间,状态转移方程计算使用 O(1)时间;
空间复杂度 O(1): 使用常数大小的额外空间;
-----------------
代码:
根据状态转移方程的递推特性,无需建立状态列表 dp,而使用一个变量 x 执行状态转移即可。
---------------
class Solution {
public int lastRemaining(int n, int m) {
int res = 0;//结果集:即最后一个元素
for (int i = 2; i <= n; i++) {
res = (res + m) % i;
}
return res;
}
}
/**
1、首先考虑第一轮, 数 m 个元素之后,要删除的元素的下标是多少,从 index 为 0 开始,
删除元素的下标就是 m - 1,因为 m 可能大于 n
所以应该是 (m - 1) % n,因为要逢 n 归零重新数,
比如一共 3 个元素,数 4 个元素之后删除一个,(4 - 1) % 3 = 0,也就是删除的是第 0 个元素
2、再考虑第二轮,第一轮删除之后,应该是从删除元素的下一个元素重新开始数,
删除元素的索引是 (m - 1) % n,那么重新开始的索引就是 m % n,
所以下一个要删除元素的索引就是 (m % n + m - 1) % n,
假设一共只有 3 个数字,那么留下的那个元素的索引其实就是 (m % n + m) % n
如果有 4 个元素,那么当只有 3 个元素的时候最后留下的那个元素的索引其实就是现在重新开始数的位置
这就很明显可以用动态规划解法,用已知解求未知解,这里的第一轮的 m % n,其实就可以当作已知解,
也可以看做是只有一个元素的时候的结果
已知只有一个元素的时候,留下的就是他本身,索引为 0,那么最开始的解可以设为 dp[1] = 0
结合上述思路,动态转移方程就是 dp[i] = (dp[i - 1] + m) % i
*/