剑指 Offer 62. 圆圈中最后剩下的数字(约瑟夫问题:数学方法;模拟法)

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/

猜你喜欢

转载自blog.csdn.net/m0_37433111/article/details/113773602