【每日算法Day 74】经典面试题:约瑟夫环,我敢打赌你一定不会最后一种方法!

简介: 【每日算法Day 74】经典面试题:约瑟夫环,我敢打赌你一定不会最后一种方法!

题目链接

LeetCode 面试题62. 圆圈中最后剩下的数字[1]

题目描述

这  个数字排成一个圆圈,从数字  开始,每次从这个圆圈里删除第  个数字。求出这个圆圈里剩下的最后一个数字。

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

示例1

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

示例2

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

说明:



题解

循环链表

用一个循环链表按顺序存储  到  中的数,然后每  个数删除掉链表中的一个结点,最后剩下的数就是答案了。

这种方法时间复杂度是  ,显然太高了,所以这里也不会给大家实现代码。

递推法

首先  个人的编号依次是  ,然后踢掉了编号为  的人,这时候剩下的人编号为  。

下一个踢掉的人就要从  开始数了,所以我们把剩下的人编号从  开始重新排个序,变成  。这样编号又变成连续的了,而问题规模缩减成了  个人。

剩下的这  个人的编号我们做一下映射,映射成  ,这样就能递推下去求解了。映射的公式就是,映射后的编号为  ,那么映射之前的编号就是  。

也就是说,如果我们求出了  个人最后剩下的人编号  ,那么  个人最后剩下的人编号就是  。

用  表示  个人最后剩下的人编号,那么就有:

这样的话时间复杂度就降到了  。

对于本题这个方法已经够用了,但是如果  非常大,比如  ,但是  不是很大,比如  ,那么这时候这种方法就会超时了。具体的题目请自行百度 HDU 3089 。

递推法加速

注意观察上面的递推式  ,如果  很小,而  很大的话,递推到后面要加很多次  才会对  求余。所以我们可以直接一下子加很多次  ,然后再求余,这样就能加速了。

加了  次  之后,递推式变成了:

假设加了  次  之后才产生了取余,那么就有  ,即  。所以每次都可以加  个  ,代码实现时用下取整,也就是  。

此外还需要注意,如果发现  ,也就是后面都不需要取余了,那么就直接一步加到底,退出循环得到答案。

这个方法时间复杂度不是很好分析,但应该也是对数级别的。

数学法

这个方法在我之前的文章中已经讲过了:

具体数学-第8课(取整进阶)韦阳的博客:具体数学-第8课(取整进阶)[2]知乎专栏:具体数学-第8课(取整进阶)[3]知乎高赞回答也整理过了一遍:世界上有哪些代码量很少,但很牛逼很经典的算法或项目案例?[4]

大致思想也是重新编号,做编号映射,但是和上面递推法不同的是复杂度降到了对数级别。

这里就不详细讲了,大家可以去看上面的文章,这里直接给出伪代码:


D = 1
while D <= (m-1)n:
    D = k
ans = mn+1-D

其中  。

不过这个这里的编号是  到  ,所以最后答案要减去  。

这种方法将时间复杂度降到了  ,用对数换底公式后得到  。

可以看出,这种方法适用于  特别大,但是  特别小的情况。否则的话如果  很大,分母会非常小,导致复杂度非常高。

代码

递推法(c++)

class Solution {
public:
    int lastRemaining(int n, int m) {
        int last = 0;
        for (int i = 2; i <= n; ++i) {
            (last += m) %= i;
        }
        return last;
    }
};

递推法加速(c++)

class Solution {
public:
    int lastRemaining(int n, int m) {
        if (m == 1) return n-1;
        int last = 0, t = 1;
        for (int i = 2; i <= n; i+=t) {
            t = (i-last+m-3) / (m-1);
            if (i+t-1 > n) {
                last += (n-i+1)*m;
                break;
            }
            (last += t*m) %= (i+t-1);
        }
        return last;
    }
};

数学法(c++)

class Solution {
public:
    int lastRemaining(int n, int m) {
        long D = 1, end = (long)n*(m-1);
        while (D <= end) {
            D = (m*D+m-2) / (m-1);
        }
        return (long)n*m-D;
    }
};
相关文章
|
1月前
|
存储 算法
【软件设计师】常见的算法设计方法——递推法
【软件设计师】常见的算法设计方法——递推法
|
19天前
|
消息中间件 算法 Java
抖音面试:说说延迟任务的调度算法?
Netty 框架是以性能著称的框架,因此在它的框架中使用了大量提升性能的机制,例如 Netty 用于实现延迟队列的时间轮调度算法就是一个典型的例子。使用时间轮调度算法可以实现海量任务新增和取消任务的时间度为 O(1),那么什么是时间轮调度算法呢?接下来我们一起来看。 ## 1.延迟任务实现 在 Netty 中,我们需要使用 HashedWheelTimer 类来实现延迟任务,例如以下代码: ```java public class DelayTaskExample { public static void main(String[] args) { System.ou
27 5
抖音面试:说说延迟任务的调度算法?
|
20小时前
|
机器学习/深度学习 算法 数据挖掘
算法金 | K-均值、层次、DBSCAN聚类方法解析
**摘要:** 这篇文章介绍了聚类分析的基本概念和几种主要的聚类算法。聚类是无监督学习中用于发现数据内在结构的技术,常用于市场分析、图像分割等场景。K-均值是一种基于划分的算法,简单高效但易受初始值影响;层次聚类包括凝聚和分裂方式,形成层次结构但计算复杂;DBSCAN基于密度,能处理任意形状的簇,但参数选择敏感。文章还讨论了这些算法的优缺点和适用场景,并提供了相关资源链接和Python实现。
19 9
算法金 | K-均值、层次、DBSCAN聚类方法解析
|
9天前
|
机器学习/深度学习 算法 C语言
详细介绍递归算法在 C 语言中的应用,包括递归的基本概念、特点、实现方法以及实际应用案例
【6月更文挑战第15天】递归算法在C语言中是强大力量的体现,通过函数调用自身解决复杂问题。递归涉及基本概念如自调用、终止条件及栈空间管理。在C中实现递归需定义递归函数,分解问题并设定停止条件。阶乘和斐波那契数列是经典应用示例,展示了递归的优雅与效率。然而,递归可能导致栈溢出,需注意优化。学习递归深化了对“分而治之”策略的理解。**
22 7
|
25天前
|
算法 数据安全/隐私保护 C++
基于二维CS-SCHT变换和扩频方法的彩色图像水印嵌入和提取算法matlab仿真
该内容是关于一个图像水印算法的描述。在MATLAB2022a中运行,算法包括水印的嵌入和提取。首先,RGB图像转换为YUV格式,然后水印通过特定规则嵌入到Y分量中,并经过Arnold置乱增强安全性。水印提取时,经过逆过程恢复,使用了二维CS-SCHT变换和噪声对比度(NC)计算来评估水印的鲁棒性。代码中展示了从RGB到YUV的转换、水印嵌入、JPEG压缩攻击模拟以及水印提取的步骤。
|
4天前
|
存储 算法 Java
面试高频算法题汇总「图文解析 + 教学视频 + 范例代码」之 二分 + 哈希表 + 堆 + 优先队列 合集
面试高频算法题汇总「图文解析 + 教学视频 + 范例代码」之 二分 + 哈希表 + 堆 + 优先队列 合集
9 0
|
1月前
|
机器学习/深度学习 算法 固态存储
深度学习算法工程师面试问题总结| 深度学习目标检测岗位面试总结
本文给大家带来的百面算法工程师是深度学习目标检测岗位面试总结,文章内总结了常见的提问问题,旨在为广大学子模拟出更贴合实际的面试问答场景。在这篇文章中,我们还将介绍一些常见的深度学习目标检测面试问题,并提供参考的回答及其理论基础,以帮助求职者更好地准备面试。通过对这些问题的理解和回答,求职者可以展现出自己的深度学习目标检测领域的专业知识、解决问题的能力以及对实际应用场景的理解。同时,这也是为了帮助求职者更好地应对深度学习目标检测岗位的面试挑战,提升面试的成功率和竞争力。
|
14天前
|
算法 数据可视化 数据挖掘
最佳加油站选择算法:解决环路加油问题的两种高效方法|LeetCode力扣134
最佳加油站选择算法:解决环路加油问题的两种高效方法|LeetCode力扣134
|
14天前
|
算法 数据挖掘 定位技术
算法必备数学基础:图论方法由浅入深实践与应用
算法必备数学基础:图论方法由浅入深实践与应用
|
14天前
|
数据采集 算法 数据挖掘
LeetCode 题目 80:删除排序数组中的重复项 II【算法面试高频题】
LeetCode 题目 80:删除排序数组中的重复项 II【算法面试高频题】