NC132 环形链表的约瑟夫问题
题目描述
编号为 1 到 n 的 n 个人围成一圈。从编号为 1 的人开始报数,报到 m 的人离开。
下一个人继续从 1 开始报数。
n-1 轮结束以后,只剩下一个人,问最后留下的这个人编号是多少?
1. 暴力解法
没什么好说的,用一个数组模拟整个过程,n-1轮循环,每轮报一次数,然后每次出局的时候剩下的数据向前平移,直到剩一个人。
class Solution {
public:
/**
*
* @param n int整型
* @param m int整型
* @return int整型
*/
int ysf(int n, int m) {
vector<int> nums(n);
for (int i = 0; i < n; i++) nums[i] = i;
int start = 0; // 数组下标为0的,存的是1号人
while (nums.size() != 1) {
// 哪个玩家该出局
int id_to_away = (start + m - 1) % nums.size();
// 数组中删掉
nums.erase(nums.begin() + id_to_away);
// 这个人下次开始报1
start = id_to_away;
}
// 题目编号从1开始,代码的下标从0开始,因此加1修正
return nums[0] + 1;
}
};
- 时间复杂度:O(n2), 两重循环。其中
erase
方法也需要对数组进行一次遍历,故总的复杂度达到了O(n2) - 空间复杂度:O(n), 维护了长度为n的数组。
2. 动态规划
方法1的时间复杂度太高了,我们看下有没有规律可以优化。
正向推导似乎很难找到规律,那么正难则反, 我们从后往前分析。
设f(n,m)为n个人玩游戏,报m的人退出的游戏,最后剩下的人的编号。我们看看是不是跟f(n−1,m)或f(n+1,m)有关系,如果是的话,就可以dp了。
假设f(n,m)已知, 那么我们进行一轮游戏后,从被淘汰的那个人开始,进行游戏f(n−1,m),应该得到同一个人。
换句话说,我们只要求出游戏f(n,m)在第一轮结束后,谁被淘汰,就得到了f(n,m)和f(n−1,m)的关系。
第一轮被淘汰的编号显然可以用m%n表示,那么我们就得出了动态规划转移式:
f(n,m)=(m%n+f(n−1,m))%n
最终的初始值肯定是f(1,m)=0. 因为只有1个人玩游戏的时候,最后剩下的人肯定是0号玩家。
但是这个式子还是比较难算,我们进一步挖掘规律。
f(1,m)=0f(2,m)=(f(1,m)+m)%2f(3,m)=(f(2,m)+m)%3...
发现只要维护前一项就可以,因此我们只需要一个变量维护即可。
class Solution {
public:
/**
*
* @param n int整型
* @param m int整型
* @return int整型
*/
int ysf(int n, int m) {
// write code here
int res = 0;
for(int i=2; i <=n;i++)
{
res = (res + m) % i;
}
// 题目编号从1开始,代码的下标从0开始,因此加1修正
return res + 1;
}
};
- 时间复杂度:O(n), 一重循环。
- 空间复杂度:O(1), 常数个变量