IT公司100题-18-圆圈中最后剩下的数字

问题描述:

n个数字(下标为0, 1, …, n-1)形成一个圆圈,从数字0开始,每次从这个圆圈中删除第m个数字(当前数字从1开始计数)。当一个数字被删除后,从被删除数字的下一个数字开始计数,继续删除第m个数字。求这个圆圈中剩下的最后一个数字。

分析:

这是有名的约瑟夫环问题

最直接的方法:

使用链表来模拟整个删除过程。因为需要n个链表节点,所以空间复杂度为O(n)。每删除一个节点,都需要m次运算,所以时间复杂度为O(mn)。

实现代码如下所示:

 1 // 18_1.cc
 2 #include <iostream>
 3 #include <list>
 4 using namespace std;
 5
 6 int josephus(size_t n, size_t m) {
 7     if (n < 1 || m < 1)
 8         return -1;
 9     else if (n == 1)
10         return 0;
11
12     list<int> jos;
13     for(size_t i = 0; i < n; i++)
14         jos.push_back(i);
15
16     list<int>::iterator it = jos.begin();
17     while(jos.size() > 1) {
18         for(size_t i = 1; i <= m; i++) {
19             it++;
20             if(it == jos.end())  // 模拟环形
21                 it = jos.begin();
22         }
23
24         list<int>::iterator next = it;
25
26         if (it == jos.begin())
27             it = jos.end();
28         it--;
29         jos.erase(it);
30         it = next;
31     }
32
33     return *it;
34 }
35
36 int main() {
37     size_t n = 10;
38     size_t m = 2;
39     cout << "The last one is: " << josephus(n, m) << endl;
40     return 0;
41 }

从数学角度推导递推公式:

(1) 最开始n个数字为:0, 1, 2, … , n-1,该序列最后剩下的数字,是关于n和m的函数,记为f(n,m)。

(2) 第一次删除的节点为:(m-1)%n,这里记为k=(m-1)%n。

(3) 删除k之后,剩下的数字序列为k+1, k+2, …, n-1, 0, 1, …, k-1。该序列最后剩下的数字,是也是关于n-1和m的函数,只是规则不通过,记为f’(n-1, m)。

(4) (1)中最后剩下的数字和(3)中最后剩下的数字是相同的,所以f(n, m) = f’(n-1, m)。

(5) 对剩下的数字序列,做一个映射p(x) = (x-(k+1))%n,映射之前数字为x,映射之后为(x-(k+1))%n,如下所示:

k+1    ->    0
k+2    ->    1

n-1     ->    n-k-2
0        ->    n-k-1

k-1     ->    n-2

在这里,很容易知道p(x)函数的逆函数为:p-1(x) = (x+(k+1))%n

(5) 映射完之后的序列,也是从0开始的,所以可以继续使用f函数来标识,记为f(n-1, m)。

f(n-1, m) = p[f‘(n-1, m)] 推出 f’(n-1, m) = p-1[f(n-1, m)] = (f(n-1, m) + (k+1))%n

将k=(m-1)%n代入得:f’(n-1, m) = (f(n-1, m) + m)%n

f(n, m) = (f(n-1, m) + m)%n

ok, 得到递推公式:

f(n, m) = 0, if n==1

f(n, m) = (f(n-1, m) + m)%n if n> 1

使用上述递推公式,时间复杂度为O(n),空间复杂度为O(1)。

实现代码如下所示:

 1 // 18_2.cc
 2 #include <iostream>
 3 using namespace std;
 4
 5 int josephus(size_t n, size_t m) {
 6     if (n < 1 || m < 1)
 7         return -1;
 8
 9     int res = 0;
10     for (int i = 2; i <= n; i++)
11         res = (res + m) % i;
12
13     return res;
14 }
15
16 int main() {
17     size_t n = 10;
18     size_t m = 2;
19     cout << "The last one is: " << josephus(n, m) << endl;
20     return 0;
21 }

IT公司100题-18-圆圈中最后剩下的数字

时间: 2024-11-08 20:26:33

IT公司100题-18-圆圈中最后剩下的数字的相关文章

IT公司100题-25-求字符串中的最长数字串

问题描述: 实现一个函数,求出字符串中的连续最长数字串.例如输入”12345cbf3456″,输出”12345″. 函数原型为: void conti_num_max( const char * src, char * dest); dest保存最长数字串,返回void. 分析: 遍历一遍字符串,记录起始位置和长度即可. 代码实现: 1 // 25.cc 2 #include <iostream> 3 #include <cstring> 4 using namespace std

IT公司100题-13-求链表中倒数第k个结点

问题描述: 输入一个单向链表,输出该链表中倒数第k个结点.链表倒数第0个节点为NULL. struct list_node { int data; list_node* next; }; 分析: 方法1: 首先计算出链表中节点的个数n,然后倒数第k个节点,为正数n-k+1个节点. 需要遍历链表2次. 方法1代码实现: 1 // 13_1.cc 2 #include <iostream> 3 using namespace std; 4 5 struct list_node { 6 int da

IT公司100题-11-求二叉树中节点的最大距离

问题描述: 写程序,求一棵二叉树中相距最远的两个节点之间的距离. 10/     \6      14/   \   /   \4    8 12    16 分析: 二叉树中最远的两个节点,要么是根和一个叶子节点,要么是两个叶子节点. 代码实现: 1 // 11.cc 2 #include <iostream> 3 using namespace std; 4 5 typedef struct BSTreeNode { 6 int data; 7 BSTreeNode *left; 8 BS

LeetCode1579题——圆圈中最后剩下的数字

1.题目描述:0,1,,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字.求出这个圆圈里剩下的最后一个数字.例如,0.1.2.3.4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2.0.4.1,因此最后剩下的数字是3. 2.示例:示例 1:输入: n = 5, m = 3输出: 3 示例 2:输入: n = 10, m = 17输出: 2 限制:1 <= n <= 10^51 <= m <= 10^6 3.解题思路(1)将

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

我的LeetCode:https://leetcode-cn.com/u/ituring/ 我的LeetCode刷题源码[GitHub]:https://github.com/izhoujie/Algorithmcii LeetCode 面试题62. 圆圈中最后剩下的数字 题目 0,1,,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字.求出这个圆圈里剩下的最后一个数字. 例如,0.1.2.3.4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字

编程算法 - 圆圈中最后剩下的数字(递推公式) 代码(C++)

圆圈中最后剩下的数字(递推公式) 代码(C++) 本文地址: http://blog.csdn.net/caroline_wendy 题目: 0,1...,n-1这n个数字排成一个圆圈, 从数字0開始每次从这个圆圈里删除第m个数字. 求出这个圆圈里最后剩下的数字. 能够推导出约瑟夫环的递推公式, 使用循环进行求解,  时间复杂度O(n), 空间复杂度O(1). 代码: /* * main.cpp * * Created on: 2014.7.12 * Author: spike */ #incl

编程算法 - 圆圈中最后剩下的数字(循环链表) 代码(C++)

圆圈中最后剩下的数字(循环链表) 代码(C++) 本文地址: http://blog.csdn.net/caroline_wendy 题目: 0,1...,n-1这n个数字排成一个圆圈, 从数字0开始每次从这个圆圈里删除第m个数字. 求出这个圆圈里最后剩下的数字. 使用循环链表, 依次遍历删除, 时间复杂度O(mn), 空间复杂度O(n). 代码: /* * main.cpp * * Created on: 2014.7.13 * Author: Spike */ #include <iostr

剑指Offer对答如流系列 - 圆圈中最后剩下的数字

面试题62:圆圈中最后剩下的数字 题目描述 0, 1, -, n-1这n个数字排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字.求出这个圆圈里剩下的最后一个数字. 例如,从数字0开始每次删除第3个数字,则删除的前四个数字是2 0 4 1 因此最后剩下的数字是3 问题分析 思路一: 既然涉及到数据的频繁删除,可以考虑使用链表来存放数据,每次对长度取余数可以实现循环操作. 思路二: 这种问题规律性非常强,其实已经有对这一规律背后的数学模型的探究,即约瑟夫环 举一个具体的场景: 据说著名犹太历

LeetCode 62. 圆圈中最后剩下的数字

面试题62. 圆圈中最后剩下的数字 难度简单 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 <= n <= 10^5 1 <= m <