2021年02月09日 周二 天气晴 【不悲叹过去,不荒废现在,不惧怕未来】
本文目录
1. 约瑟夫问题
剑指 Offer 62. 圆圈中最后剩下的数字
本题就是著名的约瑟夫问题:N个人围成一圈,第一个人从1开始报数,报M的将被杀掉,下一个人接着从1开始报。如此反复,最后剩下一个,求最后的胜利者。
2. 题解
2.1 模拟法(超时)
这种方法就是用数组模拟整个过程,核心思路是直接计算下一个待删除数的索引: idx = (idx + m - 1)%n;
时间复杂度很高,为 O(mn),leetcode会超时。
class Solution {
public:
int lastRemaining(int n, int m) {
vector<int> vec;
for(int i=0;i<n;++i)
vec.emplace_back(i);
int idx = 0;
while(n>1){
// 获取下一个待删除数的索引,要仔细理解!
idx = (idx + m - 1)%n;
vec.erase(vec.begin()+idx);
--n;
}
return vec[0];
}
};
2.2 数学解法
这道题有时间复杂度为为 O(n) 的数学解法。
核心思想: 我们知道,最后一个数的索引 sur 一定是0,所以我们开始倒推,计算2个数时 sur 的值,然后计算3个数时 sur 的值,最后计算出 N 个数(即还没有开始删除)时 sur 的值,知道刚开始时最后一个数的索引值之后,问题就解决了。具体该如何倒推呢?下面开始介绍。
图源COOLUCAS,侵删。
上图所示的是当 N = 8, m = 3 时的过程图。我们发现,每次删除一个数后,所有的数就向前移动了 m 位,从 N = 8 变成 N = 7 可以看成:所有数先向前移动 m 位(溢出的放到最后),然后删除 C。因此,往前推的话就是:先加上 C ,然后所有数向后移动 m 位。 然后就得到了递推公式:上一轮sur = (当前轮sur + m) % 上一轮剩余数字的个数。
*解释一下:“ % 上一轮剩余数字的个数 ” 其实就是 “ 先加上C ” 的体现,“ 当前轮sur + m ” 其实就是 “ 然后所有数向后移动 m 位 ” 的体现。
思想搞明白,代码就比较简单了:
class Solution {
public:
int lastRemaining(int n, int m) {
int idx = 0;
// 最后一轮剩下2个数,所以从2开始倒推
for(int i=2;i<=n;++i){
idx = (idx + m) % i;
}
return idx;
}
};
参考文献
《剑指offer 第二版》
https://blog.csdn.net/u011500062/article/details/72855826
https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/solution/huan-ge-jiao-du-ju-li-jie-jue-yue-se-fu-huan-by-as/
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/