题目:面试题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. 圆圈中最后剩下的数字的更多相关文章

  1. Java实现 LeetCode 面试题62. 圆圈中最后剩下的数字(约瑟夫环)

    面试题62. 圆圈中最后剩下的数字 0,1,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字.求出这个圆圈里剩下的最后一个数字. 例如,0.1.2.3.4这5个数字组成一个圆 ...

  2. [LeetCode]面试题62. 圆圈中最后剩下的数字(数学)

    题目 0,1,,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字.求出这个圆圈里剩下的最后一个数字. 例如,0.1.2.3.4这5个数字组成一个圆圈,从数字0开始每次删除第3 ...

  3. 【LeetCode】面试题62. 圆圈中最后剩下的数字 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 约瑟夫环 日期 题目地址:https://leetco ...

  4. 《剑指offer》面试题62. 圆圈中最后剩下的数字

    问题描述 0,1,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字.求出这个圆圈里剩下的最后一个数字. 例如,0.1.2.3.4这5个数字组成一个圆圈,从数字0开始每次删除第 ...

  5. Leetcode_面试题62. 圆圈中最后剩下的数字(约瑟夫环)

    经典的约瑟夫环,n个人排成一圈,第m个出队. 递归 code1 class Solution { public: int f(int n,int m){ if(n==1){ //递归边界,最后一个 r ...

  6. [剑指offer]62.圆圈中最后剩下的数字

    62.圆圈中最后剩下的数字 题目 0,1,...,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字.求出这个圆圈里剩下的最后一个数字. 例如,0.1.2.3.4这5个数字组成 ...

  7. 剑指 Offer 62. 圆圈中最后剩下的数字 + 约瑟夫环问题

    剑指 Offer 62. 圆圈中最后剩下的数字 Offer_62 题目描述 方法一:使用链表模拟 这种方法是暴力方法,时间复杂度为O(nm),在本题中数据量过大会超时. 方法二:递归方法 packag ...

  8. 【Java】 剑指offer(62) 圆圈中最后剩下的数字

      本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集   题目 0, 1, …, n-1这n个数字排成一个圆圈,从数字0开始每 ...

  9. 《剑指offer》面试题45 圆圈中最后剩下的数字(Java版本)

    引言 这道题网上的讲解都是直接抄书,没意思,所以想自己写一写,补充一下,便于自己理解.另外,大家都忽略了经典解法,虽然这种解法效率不及第二种,但是我觉得在项目当中阅读性其实很重要,牺牲一点点效率保证代 ...

随机推荐

  1. PAT甲级 1093 Count PAT‘s (25 分) 状态机解法

    题目 原题链接 The string APPAPT contains two PAT's as substrings. The first one is formed by the 2nd, the ...

  2. npm i安装命令中的-g -D -S的区别

    -g为全局安装 -D 对模块进行局部安装,模块写入到 devDependencies 对象 用于开发阶段,开发时用到的工具等 -S 局部安装,不同的是模块写入到 dependencies对象 用于生产 ...

  3. ES6学习笔记之 this 详解

    1.非箭头函数下的 this var obj = { x: 0, f1: function () { console.log(this.x); } } var f1 = obj.f1; var x = ...

  4. excel函数提取身份证出生日期,分离日期时间的日期和时间

    1.提取身份证出生日期 =1*TEXT(MID(H13,7,8),"0-00-00")用MID函数提取表示日期的位数,再用text函数转换为格式1998-6-21格式的文本,再通过 ...

  5. 10年程序员的编程语言感受与Go的结缘

    因为小编最近在写两套教程,分别是Java语言基础教程和Go语言的基础教程,并且这 2 门语言,小编在实际的工作中也都有用过 而且 www.helloworld.net 社区的后台,就是用Go语言写的, ...

  6. Mongo写入安全机制

    写入安全(Write Concern) 是一种客户端设置,用于控制写入的安全级别.默认况下,插入.删除和更新都会一直等待数据库响应(写入是否成功),然后才会继续执行.通常,遇到错误时,客户端会抛出一个 ...

  7. 16、lamp的搭建

    搭建web02服务器作为web01的负载均衡服务器: httpd和nginx配置比较相似,也有虚拟主机,一个http服务需要配置多个站点,基于ip(基本用不到).端口(内部网站).域名(外部网站): ...

  8. 同步工具——Exchanger

    本博客系列是学习并发编程过程中的记录总结.由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅. 并发编程系列博客传送门 本文是转载文章,原文请见这里 一.Exchanger简介 ...

  9. MongoDB数据库 5分钟快速上手

    安装 本次安装使用docker镜像,安装无障碍,一行命令即可解决 # 安装镜像并启动 可以自行修改命令中的参数 ## 账户 tanoak 密码 123123 ## -p 27018:27017 映射端 ...

  10. SonarQube集成Gitlab

    SonarQube配置gitlab Gitlab创建访问Token 拷贝此Token至Sonarqube进行配置 检查配置配置成功 可以看到权限内的项目