题目:
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 <= 10^6
延伸:
本题就是经典的约瑟夫问题:
人们站在一个等待被处决的圈子里。 计数从圆圈中的指定点开始,并沿指定方向围绕圆圈进行。 在跳过指定数量的人之后,处刑下一个人。 对剩下的人重复该过程,从下一个人开始,朝同一方向跳过相同数量的人,直到只剩下一个人,并被释放。
问题即,给定人数、起点、方向和要跳过的数字,选择初始圆圈中的位置以避免被处决。
这个问题是以弗拉维奥·约瑟夫命名的,他是1世纪的一名犹太历史学家。他在自己的日记中写道,他和他的40个战友被罗马军队包围在洞中。他们讨论是自杀还是被俘,最终决定自杀,并以抽签的方式决定谁杀掉谁。约瑟夫斯和另外一个人是最后两个留下的人。约瑟夫斯说服了那个人,他们将向罗马军队投降,不再自杀。约瑟夫斯把他的存活归因于运气或天意,他不知道是哪一个。
解法1:暴力
/**
* 思路:
* 创建长度为n的boolean类型数组,数组值为false表示未删除,true表示删除
* 创建变量count表示剩余元素个数,变量mark表示每个元素当前的标记
* 只要元素还没有到1个就一直循环
* 遍历数组寻找要删除的元素
* 如果当前下标值是true说明该元素已经被删除,忽略该元素
* 否则,mark增加,如果mark等于我们要找的m,就进行删除操作。之后重置mark,count数减一
* 最后遍历数组找到仅剩的那个为false的元素
*/
public static int lastRemaining(int n, int m) {
int count=n,mark=0;
boolean[] arr=new boolean[n];
while (count!=1){
for (int i=0;i<n;i++){
if (arr[i]==true){
continue;
}else {
mark++;
if (mark==m){
arr[i]=true;
mark=0;
count--;
}
}
if (count==1){
break;
}
}
}
for (int i=0;i<n;i++){
if (arr[i]==false){
return i;
}
}
return -1;
}
时间复杂度:On^2
空间复杂度:On
解法2:链表
/**
*思路:
* 创建变量size表示链表中节点个数,变量mark表示每个元素当前的标记
* 创建链表记录所有元素
* 链表节点数不等于一就不断循环
* 遍历链表,当mark等于m时,删除节点,重置mark,size减一
* 返回仅剩的节点值
*/
public static int lastRemaining(int n, int m) {
int mark = 0, size = n;
ArrayList<Integer> list = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
list.add(i);
}
LinkedList<Integer> linked = new LinkedList<>(list);
while (size!=1) {
Iterator<Integer> iterator = linked.iterator();
while (iterator.hasNext()) {
mark++;
//移动元素
iterator.next();
if (mark == m) {
iterator.remove();
mark = 0;
size--;
}
if (size == 1) {
break;
}
}
}
return linked.get(0);
}
时间复杂度:On^2
空间复杂度:On
解法3:数学
/**
*思路:
* lastRemaining(5,3)基本情况
* 01234
* 3401
* 134
* 13
* 3
* 每次删除一个元素后,相当于数组整体前移3
* 倒推最后存活的下标,反之,每次相当于数组整体后移3,考虑越界问题,模以当前长度
* 3 (0+3)%2=1
* 13 (1+3)%3=1
* 134 (1+3)%4=0
* 3401 (0+3)%5=3
* 01234
* 得出公式:x=(x+m)%i
*/
public static int lastRemaining(int n, int m) {
int x = 0;
for (int i = 2; i <= n; i++) {
x = (x + m) % i;
}
return x;
}
时间复杂度:On
空间复杂度:O1