约瑟夫环问题

直接看对应的题目:
圆圈中最后剩下的数字
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof
0,1,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。

例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。

示例 1:

输入: n = 5, m = 3
输出: 3

示例 2:

输入: n = 10, m = 17
输出: 2

限制

  • 1 <= n <= 10^5
  • 1 <= m <= 10^6

思路
最简单的思路就是直接用链表模拟这个过程。对于C++而言可以用标准库中的list,然而时间复杂度太高了,应该是O(mn)。下面的代码我在力扣里直接判断超时了。

不过这里还是写一下,也算是复习list的用法,并且一个有知识点是 list 用 erase 删除节点,会返回后面一个节点的迭代器。

超时的C++代码

class Solution {
public:
    int lastRemaining(int n, int m) {
        list<int> ring;
        for (int i = 0; i < n; ++i) {
            ring.push_back(i);
        }
        auto it = ring.begin();
        while (ring.size() > 1) {
            int num = 0;
            while (num < m - 1) {
                if (it == ring.end()) {
                    it = ring.begin();
                }
                ++it;
                ++num;
            }
            if (it == ring.end()) {
                it = ring.begin();
            }
            it = ring.erase(it);//list迭代器删除会自动返回下一个迭代器
        }
        return *(ring.begin());
    }
};

想要写出时间复杂度低的不超时的代码,还得用数学推导的方式推导出一个递推公式。

f(n,m) = [m+f(n−1,m)]%n

公式的证明太硬,本人几次看懂几次忘记,想来也是没有真正懂,力扣题解里有很好的证明,同时也有用图像进行演示的,因此这里也不 详述证明过程,只谈谈个人对公式的具体含义的理解:

公式的大致意思是长度为 n 的序列经过约瑟夫不停地往后删除第 m 个元素的操作后最终留下的数值的索引 f(n,m) 可以通过长度为 n-1 的序列中最终留下的数字的索引 f(n-1,m) 计算得到。这样我们只要不停得递归,算到 f(1,m),由于长度为 1 的序列最终留下的数字的索引显然是 0,因此就能计算出最终结果。

虽然不证明,但还是最好用一个实例加深理解(其实根本没理解,用加深记忆更为妥当)。这里思考示例1,示例1中最终留下是数字索引 f(n,m) = f(5,3) = 3,然后我们在纸上演算一下 f(4,3) ,发现删除到最后留下的数字索引为 0 ,[m+f(n−1,m)]%n = (3+0)%5 = 3,这显然是符合公式的。

上面提到递归,但其实用迭代方法直接倒着算就可以了。通过该方法编写的代码时间复杂度是O(n),比起上述模拟整个过程的方法大大降低。

不超时的C++代码

class Solution {
public:
    int lastRemaining(int n, int m) {
        int res = 0;
        for(int i = 2; i <= n; ++i){
            res = (m + res) % i;
        }
        return res;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值