【算法学习】约瑟夫环(含公式思想总结)(Java)

一、题目描述

这个问题是以弗拉维奥·约瑟夫命名的,他是1世纪的一名犹太历史学家。他在自己的日记中写道,他和他的40个战友被罗马军队包围在洞中。他们讨论是自杀还是被俘,最终决定自杀,并以抽签的方式决定谁杀掉谁。约瑟夫斯和另外一个人是最后两个留下的人。约瑟夫斯说服了那个人,他们将向罗马军队投降,不再自杀。约瑟夫斯把他的存活归因于运气或天意,他不知道是哪一个。 ——【约瑟夫问题】维基百科

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:模拟

通过循环链表,循环模拟数字剔除,进过测试,执行超时。

思路2:数学方法

不得不说,通过数学推导归纳得出公式后写代码,不仅简洁,执行效率也很高

递推公式:

f ( n , m ) = { 0 , n = 0 ( f ( n − 1 , m ) + m ) % n n > 0 f(n,m) = \begin{cases} 0, & \text{n = 0} \\ (f(n-1, m) + m)\%n & \text{n > 0} \\ \end{cases} f(n,m)={0,(f(n1,m)+m)%nn = 0n > 0

看了很多leetcode上的题解,以及约瑟夫环公式的原理推导,讲的都比较抽象,自己总结如下推导。

推导思路

以初始化有 n 个数,没 m 个剔除,使用 L(n) 代表长度为 n 的序列,设解为 F(n, m) 使用大 F 仅仅因为小 f 不太好看,并没有别的意图

  1. 首先将问题定位为递归分治问题(一方面是看题解得出,另一方面据说“万物皆可分治”)
  2. 既然是递归问题,就可以提出假设,假设 F(n-1) 已经算好了。
  3. 基于思路2问题已经十分简单了,只需要根据 F(n-1, m) 推导出 F(n, m) 即可

针对思路3 力扣 题解中给出很多种倒推,看了好几篇不知其所以然。实际上就是为什么可以倒推如何倒推的问题,题解中大多只给出如何倒推。

why 为什么可以倒推

可以倒推的原因是因为 L(n-1) 的序列是从 L(n) 序列里剔除一个数,并调整顺序得来,具体如下图所示:
在这里插入图片描述
L(n) 实际上等价于 L(n - 1) 在最后补个 2(或者说,在L(n - 1)后补个 2, 与 L(n) 计算得出结果存在关系),因此,可以通过 L(n - 1) 序列的结果反推,L(n) 的结果。

为什么能等价呢? 约瑟夫环,【环】,列顺序一样结果就一样,上图中 L(n - 1) 对应的环,就等价于 L(n) 删除 2 后第二轮报数 (反之,L(n-1) 补全 2 再回退一次报数,就等价于 L(n) 初始状态) 如下图所示:
在这里插入图片描述

how 如何倒推

明白了为什么可以倒推,如何倒推就很简单了,首先对上图中 L(n-1) 进行简化,如下图所示:
在这里插入图片描述
L’(n-1) 是从 0 开始新序列,L’(n-1) 的结果通过 思路2 中假设 F(n-1, m) 已经被计算出来了是 X’,即 F(n-1, m) = X’。那么,转换到 L(n-1) 中可通过 X = (X’ + m) % n 得出。首先 +m 是从 0 开始和从 3 开始的对齐, %n 是因为 m 可能大于 n。

至此,就可以推算出上文提到的公式

三、参考代码

class Solution {
    public int lastRemaining(int n, int m) {
        if(n == 1) return 0;
        
        int x = lastRemaining(n-1, m);
        return (x + m) % n;
    }
}

四、测试连接

力扣

鼠小 CSDN认证博客专家 一个萌汉子
未来的路是黑的,我不知道怎么走,我需要做的就是先走着。https://smallzheng.blog.csdn.net/
算法是一切程序设计的基础和灵魂,更是一位程序员编程水平高低的集中体现。 涵盖广泛:精炼的理论讲述嵌入经典算法示例,学习查询兼而有之。 阐述到位:算法思想算法实现和经典面试题合理搭配,相辅相成。 实例完善:分析精准,注释精确,保证每段代码皆可通过编译执行。 超过600分钟讲解视频和案例源代码倾囊相送。 附赠5本电子书教程铺就Java程序员成长之路。 本书分三篇,共14章,分别介绍了算法基础、算法应用和算法面试题。首先介绍了算法概述,然后重点分析了数据结构和基本算法思想;接着详细讲解了算法在排序、查找、数学计算、数论、历史趣题、游戏等领域中的应用;后梳理和精选了一些经典的算法面试题,供读者开拓思维之用。 第1章 算法和实现算法Java语法 1.1 建立算法初步概念 1.1.1 什么是算法 1.1.2 算法的发展历史 1.1.3 算法的分类 1.2 算法相关概念的区别 1.2.1 算法公式的关系 1.2.2 算法与程序的关系 1.2.3 算法与数据结构的关系 1.3 算法的表示 1.3.1 自然语言表示 1.3.2 流程图表示 1.3.3 N-S图表示 1.3.4 伪代码表示 1.4 算法的性能评价 1.4.1 时间复杂度 1.4.2 空间复杂度 1.5 一个算法实例 1.5.1 查找数字 1.5.2 创建项目 1.5.3 编译执行 1.6 Java程序的基本结构 1.6.1 类是一个基本单元 1.6.2 main方法 1.6.3 自定义方法 1.6.4 System.out.println的使用 1.6.5 一个简单而完整的程序 1.7 顺序结构 1.8 分支结构 1.8.1 if...else分支结构 1.8.2 if...else嵌套 1.8.3 switch语句 1.8.4 编程实例 1.9 循环结构 1.9.1 while循环 1.9.2 do…while循环 1.9.3 for循环 1.9.4 编程实例 1.10 跳转结构 1.10.1 break 1.10.2 continue 1.10.3 编程实例 1.11 小结 第2章 数据结构 2.1 数据结构概述 2.1.1 什么是数据结构 2.1.2 数据结构中的基本概念 2.1.3 数据结构的内容 2.1.4 数据结构的分类 2.1.5 数据结构的几种存储方式 2.1.6 数据类型 2.1.7 常用的数据结构 2.1.8 选择合适的数据结构解决实际问题 2.2 线性表 2.2.1 什么是线性表 2.2.2 线性表的基本运算 2.3 顺序表结构 2.3.1 准备数据 2.3.2 初始化顺序表 2.3.3 计算顺序表长度 2.3.4 插入结点 2.3.5 追加结点 2.3.6 删除结点 2.3.7 查找结点 2.3.8 显示所有结点 2.3.9 顺序表操作实例 2.4 链表结构 2.4.1 什么是链表结构 2.4.2 准备数据 2.4.3 追加结点 2.4.4 插入头结点 2.4.5 查找结点 2.4.6 插入结点 2.4.7 删除结点 2.4.8 计算链表长度 2.4.9 显示所有结点 2.4.10 链表操作实例 2.5 栈结构 2.5.1 什么是栈结构 2.5.2 准备数据 2.5.3 初始化栈结构 2.5.4 判断空栈 2.5.5 判断满栈 2.5.6 清空栈 2.5.7 释放空间 2.5.8 入栈 2.5.9 出栈 2.5.10 读结点数据 2.5.11 栈结构操作实例 2.6 队列结构 2.6.1 什么是队列结构 2.6.2 准备数据 2.6.3 初始化队列结构 2.6.4 判断空队列 2.6.5 判断满队列 2.6.6 清空队列 2.6.7 释放空间 2.6.8 入队列 2.6.9 出队列 2.6.10 读结点数据 2.6.11 计算队列长度 2.6.12 队列结构操作实例 2.7 树结构 2.7.1 什么是树结构 2.7.2 树的基本概念 2.7.3 二叉树 2.7.4 准备数据 2.7.5 初始化二叉树 2.7.6 添加结点 2.7.7 查找结点 2.7.8 获取左子树 2.7.9 获取右子树 2.7.10 判断空树 2.7.11 计算二叉树深度 2.7.12 清空二叉树 2.7.13 显示结点数据 2.7.14 遍历二叉树 2.7.15 树结构操作实例 2.8 图结构 2.8.1 什么是图结构 2.8.2 图的基本概念 2.8.3 准备数据 2.8.4 创建图 2.8.5 清空图 2.8.6 显示图 2.8.7 遍历图 2.8.8 图结构操作实例 2.9 小结 第3章 基本算法思想 3.1 常用算法思想概述 3.2 穷举算法思想 3.2.1 穷举算法基本思想 3.2.2 穷举算法实例 3.3 递推算法思想 3.3.1 递推算法基本思想 3.3.2
相关推荐
©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页