🧮 Algorithm Notebook
1、算法准备
1.1 牛客网编程OJ的典型输入输出
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.1.9 剪绳子
2.1.10 正则表达式匹配
2.2 二分查找
2.2.1 二分查找框架
2.2.2 搜索旋转排序数组
2.3 链表
2.3.1 反转链表
2.3.2 相交链表
2.3.3 合并链表
2.3.4 链表中倒数第k个节点
2.3.5 复制带随机指针的链表
2.4 排序算法
2.4.1 常见排序算法
2.5 二叉树
2.5.1 二叉树遍历
2.5.2 岛屿问题
2.5.3 二叉树路径问题
2.5.4 构造二叉树
2.6 回溯算法
2.6.1 回溯算法解题框架
2.6.2 N皇后
2.7 数组
2.7.1 删除有序数组中的重复项
2.7.2 滑动窗口最大值
2.7.3 调整数组顺序使奇数位于偶数前面
2.7.4 螺旋矩阵
2.7.5 多数元素
2.7.6 最大数
2.7.7 和为s的两个数字
2.7.8 构建乘积数组
2.7.9 两数之和
2.8 字符串
2.8.1 最小覆盖子串
2.8.2 比较版本号
2.8.3 验证IP地址
2.8.4 基本计算器 II
2.8.5 字符串解码
2.8.6 移掉 K 位数字
2.8.7 无重复字符的最长子串
2.8.8 第一个只出现一次的字符
2.8.9 翻转字符串里的单词
2.8.10 字符串转换整数 (atoi)
2.8.11 字符串四则运算
2.9 栈
2.9.1 最小栈
2.9.2 弹出序列
2.10 数学
2.10.1 用 Rand7() 实现 Rand10()
2.10.2 只出现一次的数字
2.10.3 整数反转
2.10.4 求1+2+…+n
2.10.5 二进制中1的个数
2.10.6 幂运算
2.10.7 1~n 整数中 1 出现的次数
2.10.8 数字序列中某一位的数字
2.10.9 丑数
2.10.10 n个骰子的点数
2.10.11 圆圈中最后剩下的数字
2.10.12 不用加减乘除做加法
2.10.13 x 的平方根
2.11 设计
2.11.1 LRU 缓存机制
-
+
tourist
register
Sign in
圆圈中最后剩下的数字
## 1 题目 0,1,···,n-1 这 n 个数字排成一个圆圈,从数字 0 开始,每次从这个圆圈里删除第 m 个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。 例如,0、1、2、3、4 这 5 个数字组成一个圆圈,从数字 0 开始每次删除第 3 个数字,则删除的前 4 个数字依次是 2、0、4、1,因此最后剩下的数字是 3。 **示例 1:** ```txt 输入: n = 5, m = 3 输出: 3 ``` **示例 2:** ```txt 输入: n = 10, m = 17 输出: 2 ``` **限制:** * 1 <= n <= 10^5 * 1 <= m <= 10^6 ## 2 问题分析 1. 该题目是经典的[约瑟夫问题](https://zh.wikipedia.org/wiki/%E7%BA%A6%E7%91%9F%E5%A4%AB%E6%96%AF%E9%97%AE%E9%A2%98),即: 1. $N$**个人围成一圈**,**第一个人从 1 开始报数**,**报 $M$ 的将被杀掉**,**下一个接着从 1 开始报**,**如此反复**,**最后剩下一个**,**求最后的胜利者**。 2. **约瑟夫环是一个经典的数学问题**,**我们不难发现这样的依次报数**,**似乎有规律可循**,**为了方便导出递推式**,**我们重新定义一下题目**: 1. **$N$ 个人编号为 $ 1, 2,..., N$**,**依次报数**,**每报到 $M$ 时**,**杀掉那个人**,**求最后胜利者的编号**。 3. 下面我们开始求约瑟夫环的递推公式: 1. **下面我们不用字母表示一个人**,**而用数字**: $$ 1、2、3、4、5、6、7、8、9、10、11 $$ 2. **上面的数字表示 11 个人**,**他们先排成一排**,**假设每报到 3 的人被杀掉**: 1. **刚开始时**,**头一个人编号是 1**,**从他开始报数**,**第一轮被杀掉的是编号 3 的人**。 2. **编号 4 的人从 1 开始报数**,**这时候我们可以认为编号 4 这个人是队伍的头**,**第二轮被杀掉的是编号 6 的人**。 3. **编号 7 的人开始重新报数**,**这个时候我们可以认为编号 7 这个人是队伍的头**,**第三轮杀掉的是编号 9 的人**。 4. ...... 5. **第九轮时**,**编号 2 的人开始重新报数**,**这时候我们可以认为编号 2 这个人是队伍的头**,**这轮被杀掉的是编号 8 的人**。 6. **下一个人还是编号为 2 的人**,**他从 1 开始报数**,**不幸的是他在这轮被杀掉了**。 7. **最后的胜利者是编号为 7 的人**。 3. 下图表示这一过程(先忽视绿色的一行): ![这里写图片描述](/media/202202/2022-02-12_1649310.6907512898666288.png) 4. 我们定义 $f(n, m)$**表示 $N$ 个人报数**,**每报到 $M$ 时杀掉那个人**,**最终胜利者的编号**,下面我们来推导具体的递推公式: 1. **假设我们已经知道 11 个人时胜利者的下标位置为 6**,**在求下一轮 10 个人时**,**由于第一轮删掉编号为 3 的人后**,**之后的人都往前面移动了 3 位**,**胜利者也往前移动了 3 位**,**所以最终胜利者的下标位置由 6 变成 3**。 2. **假设我们已经知道 10 个人时胜利者的下标位置为 3**,**求上一轮 11 个人时胜利者的下标位置可以看作是上一问题的逆过程**,**大家都往后移动 3 位**,**所以 $f(11, 3) = f(10, 3) + 3$**,**不过有可能数组会越界**,**所以最后模上当前人数的个数**,**即 $f(11, 3) = f(10, 3) \% 11$**。 3. **现在人数改为 $N$**,**报到 $M$ 时**,**把那个人杀掉**,**因为每杀掉一个人**,**下一个人成为头**,**相当于把数组向前移动 $M$ 位**,**若已知 $N - 1$ 个人时**,**胜利者的下标位置为 $f(N - 1, M)$**,**则 $N$ 个人的时候**,**就是往后移动 $M$ 位**,**因为有可能数组越界**,**越界的部分会被接到头上**,**所以还要模 $N$**,**即 $f(N, M) = (f(N - 1, M) + M) \% n$**。 5. 因此最终的递推公式为: $$ f(N, M) = (f(N - 1, M) + M) \% n $$ ## 3 参考代码 ```java /** * 剑指 Offer 62. 圆圈中最后剩下的数字 * @param n 数字的个数 * @param m 删除数字时的间隔 * @return 圆圈里剩下的最后一个数字 */ public int lastRemaining(int n, int m) { int dp = 0; for (int i = 2; i <= n; i++) { dp = (dp + m) % i; } return dp; } ``` ## 参考文献 1. [剑指 Offer 62. 圆圈中最后剩下的数字](https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof)。 2. [约瑟夫环——公式法(递推公式)](https://blog.csdn.net/u011500062/article/details/72855826)。
ricear
June 20, 2022, 2:25 p.m.
©
BY-NC-ND(4.0)
转发文档
Collection documents
Last
Next
手机扫码
Copy link
手机扫一扫转发分享
Copy link
Markdown文件
share
link
type
password
Update password