题目描述
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
思路分析
约瑟夫环问题。
抽象一下,0-n-1的数,循环每m个就弹出第m个数,直到剩最后一个数。
- 递归解法,根据数学归纳法得到x’=(x+k)%n,可以总结把长度为n的环的解看作是长度为n-1的解再加上报数的长度m。
- 非递归解法,使用list模拟循环链表,用cur作为指向list的下标位置。每次循环m次,将cur位置的数移出,直到list的长度为1,所剩的数即为所求。
注:力扣上纯模拟会超时,因为模拟链表的时间复杂度是O(n^2),按经验来说10^5以内是没问题的,但m最大取10^6绝对超时。
代码实现
/**
* 递归解法,把长度为n的环的解看作是长度为n-1的解再加上报数的长度m
* @param n 环里存在的数
* @param m 每隔多少挑一次
* @return
*/
public int lastRemainingSolution(int n, int m) {
if (n == 0) {
return -1;
}
if (n == 1) {
return 0;
}
//不知道n和m的大小,做取余
return (lastRemainingSolution(n - 1, m) + m) % n;
}
/**
* 非递归解法,使用list模拟,提供一个指针用于操作
* @param n
* @param m
* @return
*/
public int lastRemainingSolution2(int n, int m) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < n; i++) {
list.add(i);
}
int cur = -1;
while (list.size()>1){
for (int i = 0; i < m; i++) {
cur++;
if(cur==list.size()){
cur=0;
}
}
list.remove(cur);
//将cur移出后,将指针前调
cur--;
}
return list.get(0);
}