约瑟夫问题I
已知n个人坐成一圈,按顺时针由1开始给大家编号。然后由第一个人开始顺时针循环报数,数到m的人出局,循环此过程直到最后只剩一个人。给定两个int n和m,要求编写函数返回最后一个人的编号。
https://leetcode-cn.com/circle/article/BOoxAL/
【方法1】循环链表(队列):
时间复杂度O(m*n), 空间复杂度O(n)
import java.util.*;
public class Joseph {
public int getResult(int n, int m) {
ListNode head = new ListNode();
ListNode p = head;
for (int i = 1; i < n; i++) {
p.val = i;
p.next = new ListNode();
p = p.next;
}
p.val = n;
p.next = head;
while(p.next != p) {
for (int i = 1; i < m; i++) {
p = p.next;
}
if (p.next == p) {
return p.val;
}
p.next = p.next.next;
}
return p.val;
}
}
class ListNode{
int val;
ListNode next;
public ListNode() {
}
public ListNode(int val) {
this.val = val;
}
}
【方法2】公式
设当前报号人编号为index
,则第m个人的编号为index+m-1
,此时设剩余人数为len
下
一
个
淘
汰
的
人
=
{
i
n
d
e
x
+
m
−
1
,
m
<
=
l
e
n
(
i
n
d
e
x
+
m
−
1
)
%
l
e
n
,
m
>
l
e
n
下一个淘汰的人 = \begin{cases} index+m-1, && m <= len\\ (index+m-1)\%len, && m > len\\ \end{cases}
下一个淘汰的人={index+m−1,(index+m−1)%len,m<=lenm>len
所以每次从编号为index的人开始报数,则被淘汰的人编号为
index = (index+m-1)\%len
import java.util.*;
public class Joseph {
public int getResult(int n, int m) {
List<Integer> list = new ArrayList<>();
for (int i = 1; i <= n; i++) {
list.add(i);
}
// 开始报数人的编号的索引
int index = 0;
while (list.size() > 1) {
index = (index + m - 1) % list.size();
list.remove(index);
}
return list.get(0);
}
}
【方法3递归】
递归公式
f(n,m)=(f(n-1,m)+m-1)%n+1
f(n,m)指n个人,报第m个编号淘汰最终编号
推导过程:
-
当n=1时,最后一个淘汰也是第一个淘汰的为编号1
-
当n>1时,如下图所示
可以看出,设原编号为i,新编号为j,则可得到
i = (j + m - 1) % n + 1
这样,我们就不用模拟操作,可以直接从数值的关系找到递推的关系,可以轻轻松松的写下代码:
import java.util.*;
public class Joseph {
public int getResult(int n, int m) {
if (n == 1) {
return 1;
}
return (getResult(n-1, m) + m - 1) % n + 1;
}
}
迭代方式如下:
public class Joseph {
public int getResult(int n, int m) {
int value = 1;
for (int i = 1; i <= n; i++) {
value = (value + m - 1) % i + 1;
}
return value;
}
}
约瑟夫问题II
现有n个人围坐一圈,顺时针给大家编号,第一个人编号为1,然后顺时针开始报数。第一轮依次报1,2,1,2…没报1的人出局。接着第二轮再从上一轮最后一个报数的人开始依次报1,2,3,1,2,3…没报1的人都出局。以此类推直到剩下以后一个人。现给定一个int n,要求返回最后一个人的编号。
【方法1】循环队列模拟
public class Joseph {
public int getResult(int n) {
ArrayList<Integer> left = new ArrayList<>();
for (int i = 1; i <= n; i++) {
left.add(i);
}
int m = 2;
while (left.size() > m) {
ArrayList<Integer> tmp = new ArrayList<>();
// 为形成环预先占一个位置
tmp.add(0);
for (int i = 0; i < left.size(); i+=m) {
tmp.add(left.get(i));
}
tmp.set(0, tmp.remove(tmp.size() - 1));
left = tmp;
m++;
}
return left.get(0);
}
}
【方法2】递归
递归公式
f
(
n
,
m
)
=
(
f
(
l
e
f
t
,
m
+
1
)
−
2
)
∗
m
+
1
,
其
中
l
e
f
t
=
{
n
/
m
,
n
%
m
=
=
0
n
/
m
+
1
,
n
%
m
!
=
0
f(n, m) = (f(left, m + 1) -2) * m + 1, 其中left = \begin{cases} n / m, && n \% m == 0\\ n / m + 1, && n \% m != 0\\ \end{cases}
f(n,m)=(f(left,m+1)−2)∗m+1,其中left={n/m,n/m+1,n%m==0n%m!=0
import java.util.*;
public class Joseph {
public int getResult(int n) {
return joseph(n, 2);
}
public int joseph(int n, int m) {
int left = n % m == 0 ? n / m : n / m + 1;
if (left <= m + 1) {
return (left - 1) * m + 1;
}
int next = joseph(left, m + 1);
return (next - 2) * m + 1;
}
}