【LeetCode】面试题62. 圆圈中最后剩下的数字
这题很有意思,也很巧妙,故记录下来。
官方题解思路,是约瑟夫环的数学解法:
我们将上述问题建模为函数 f(n, m),该函数的返回值为最终留下的元素的序号。
首先,长度为n的序列会先删除第m % n个元素,然后剩下一个长度为n - 1的序列。那么,我们可以递归地求解f(n - 1, m),就可以知道对于剩下的n - 1个元素,最终会留下第几个元素,我们设答案为x = f(n - 1, m)。
由于我们删除了第m % n个元素,将序列的长度变为n - 1。当我们知道了f(n - 1, m)对应的答案x之后,我们也就可以知道,长度为n的序列最后一个删除的元素,应当是从 m % n开始数的第x个元素。因此有f(n - 1, m) = (m % n + x) % n = (m + x) % n。
我们递归计算f(n, m),f(n - 1, m),f(n - 2, m), ... 直到递归的终点f(1, m)。当序列长度为1时,一定会留下唯一的那个元素,它的编号为0。
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/solution/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-by-lee/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
需要注意的是下标的细节(老生常谈了):这里的数组 index = number,但 m 是从1开始计数。f() 的返回值 x 应该是从0开始计数的number。上文中“应当是从m % n开始数的第x个元素。”这句话里的m % n是被删除的元素的下一个元素的index,从它开始的index为x的元素正是下一步要删除的元素在原数组里的index。
Java递归代码如下:
class Solution {
public int lastRemaining(int n, int m) {
return f(n, m);
}
private int f(int n, int m) {
if (n == 1) return 0;
int x = f(n - 1, m);
return (x + m) % n;
}
}
// 13 ms / 41.8 MB 37.71% / -%
- 时间复杂度:
O(n),需要求解的函数值有n个。 - 空间复杂度:
O(n),函数的递归深度为n,需要使用O(n)的栈空间。
写成迭代,代码如下:
class Solution {
public int lastRemaining(int n, int m) {
int index = 0;
for (int i = 2; i <= n; i++) {
index = (index + m) % i;
}
return index;
}
}
// 7 ms / 36.6 MB 99.88% / -%
- 时间复杂度:
O(n),需要求解的函数值有n个。 - 空间复杂度:
O(1),只使用常数个变量。
总的来说迭代的时间和空间都更少。而且迭代的写法其实有点像动态规划了。
以下是踩坑过程:
我首先想的是,建一个首尾相连的链表,然后暴力跑,可想而知的超时,只通过了26/36个测试用例。
然后想了下优化策略,每次访问所需的步数可以通过模运算减少,然后调整了一下每次访问的过程,虽然多通过了一个测试用例,但还是超时。
class Solution {
public int lastRemaining(int n, int m) {
Node head = new Node(-1);
Node p = head;
Node q = head;
for (int i = 0; i < n; i++) {
p.next = new Node(i);
p = p.next;
}
int step = 0;
while (n > 1) {
step = (step + m - 1) % n + 1;
q = head;
p = head;
for (int i = 0; i < step; i++) {
q = p;
p = p.next;
}
//System.out.println(p.value);
q.next = p.next;
step--;
n--;
}
return head.next.value;
}
}
class Node {
int value;
Node next;
Node(int value) {
this.value = value;
}
}
然后把数据存到 LinkedList 中去试试,发现效果差不多,还是超时。结果没想到换成 ArrayList 之后居然能通过,看来在数据访问和数据移动的性能取舍之间有一个微妙的平衡,本题是倾向于需要更快的访问速度。
class Solution {
public int lastRemaining(int n, int m) {
// List<Integer> list = new LinkedList<>();
List<Integer> list = new ArrayList<>();
for (int i = 0; i < n; i++) {
list.add(i);
}
int index = 0;
while (list.size() != 1) {
index = (index + m - 1) % list.size();
// System.out.println(list.get(index));
list.remove(index);
}
// System.out.println(Arrays.toString(list.toArray()));
return list.get(0);
}
}
// 1122 ms / 43.1 MB 16.61% / -%
【LeetCode】面试题62. 圆圈中最后剩下的数字的更多相关文章
- Java实现 LeetCode 面试题62. 圆圈中最后剩下的数字(约瑟夫环)
面试题62. 圆圈中最后剩下的数字 0,1,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字.求出这个圆圈里剩下的最后一个数字. 例如,0.1.2.3.4这5个数字组成一个圆 ...
- [LeetCode]面试题62. 圆圈中最后剩下的数字(数学)
题目 0,1,,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字.求出这个圆圈里剩下的最后一个数字. 例如,0.1.2.3.4这5个数字组成一个圆圈,从数字0开始每次删除第3 ...
- 【LeetCode】面试题62. 圆圈中最后剩下的数字 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 约瑟夫环 日期 题目地址:https://leetco ...
- 《剑指offer》面试题62. 圆圈中最后剩下的数字
问题描述 0,1,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字.求出这个圆圈里剩下的最后一个数字. 例如,0.1.2.3.4这5个数字组成一个圆圈,从数字0开始每次删除第 ...
- Leetcode_面试题62. 圆圈中最后剩下的数字(约瑟夫环)
经典的约瑟夫环,n个人排成一圈,第m个出队. 递归 code1 class Solution { public: int f(int n,int m){ if(n==1){ //递归边界,最后一个 r ...
- [剑指offer]62.圆圈中最后剩下的数字
62.圆圈中最后剩下的数字 题目 0,1,...,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字.求出这个圆圈里剩下的最后一个数字. 例如,0.1.2.3.4这5个数字组成 ...
- 剑指 Offer 62. 圆圈中最后剩下的数字 + 约瑟夫环问题
剑指 Offer 62. 圆圈中最后剩下的数字 Offer_62 题目描述 方法一:使用链表模拟 这种方法是暴力方法,时间复杂度为O(nm),在本题中数据量过大会超时. 方法二:递归方法 packag ...
- 【Java】 剑指offer(62) 圆圈中最后剩下的数字
本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集 题目 0, 1, …, n-1这n个数字排成一个圆圈,从数字0开始每 ...
- 《剑指offer》面试题45 圆圈中最后剩下的数字(Java版本)
引言 这道题网上的讲解都是直接抄书,没意思,所以想自己写一写,补充一下,便于自己理解.另外,大家都忽略了经典解法,虽然这种解法效率不及第二种,但是我觉得在项目当中阅读性其实很重要,牺牲一点点效率保证代 ...
随机推荐
- jdk,jre.jvm三者的关系
jdk>jre>jvm jdk=jre+java的开发工具(包括java.exe,javac.exe.javadoc.exe) jre=jvm+java核心类库
- spring boot使用@Async异步注解
1.java的大部分接口的方法都是串行执行的,但是有些业务场景是不需要同步返回结果的,可以把结果直接返回,具体业务异步执行,也有些业务接口是需要并行获取数据,最后把数据聚合在统一返回给前端. 通常我们 ...
- 1.5w字 + 24张图肝翻 TCP。
TCP 是一种面向连接的单播协议,在 TCP 中,并不存在多播.广播的这种行为,因为 TCP 报文段中能明确发送方和接受方的 IP 地址. 在发送数据前,相互通信的双方(即发送方和接受方)需要建立一条 ...
- 带你了解Java的序列化与反序列化
什么是序列化 序列化:将 Java 对象转换成字节流的过程. 什么是反序列化 反序列化:将字节流转换成 Java 对象的过程. 序列化的实现 当 Java 对象需要在网络上传输 或者 持久化存储到文件 ...
- LCD1602液晶显示模块的单片机驱动深入详解之软件篇(AVR)
LCD1602液晶显示模块的驱动虽然比七段数码管之类的显示要复杂一些,但实际上也并不是很难,最主要的还是初始化,为什么这么说呢?我们在调试一块新液晶屏的时候,都会先初始化看看有没有光标在闪,没有光标前 ...
- 基于 electron 实现简单易用的抓包、mock 工具
背景 经常我们要去看一些页面所发出的请求时,经常会用到 Charles 做为抓包工具来进行接口抓取,但一方面市面是很多抓包工具都是收费或者无法二次开发的.当前我们团队大多数用的也都是 Charles, ...
- Springboot:单元测试@FixMethodOrder注解指定测试方法的执行顺序
我们在写JUnit测试用例时,有时候需要按照定义顺序执行我们的单元测试方法,比如如在测试数据库相关的用例时候要按照测试插入.查询.删除的顺序测试.如果不按照这个顺序测试可能会出现问题,比如删除方法在前 ...
- final添加内存屏障问题
看了 why大佬的 博客一个困扰我122天的技术问题,我好像知道答案了. 发现他留了个坑,在变量i类型为 int 或者 Integer 时,int类型的i死循环了而Integer类型的i可以结束 in ...
- SpringCloud的Ribbon自定义负载均衡算法
1.Ribbon默认使用RoundRobinRule策略轮询选择server 策略名 策略声明 策略描述 实现说明 BestAvailableRule public class BestAvailab ...
- Linux | 文本编辑器vim
vim 编辑器介绍 vim 编辑器是一款非常棒的文本处理工具,它会默认安装在当前所有的 Linux 操作系统上面. vim 编辑器中设置了三种模式,可以极大的提高我们的工作效率: 命令模式:控制光标移 ...