一、原题目
剑指Offer62:圆圈中最后剩下的数字
0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
自己的做法:模拟为圆,删除元素之后计算下一个下标,直到删除至只剩一个元素
class Solution {
public int lastRemaining(int n, int m) {
ArrayList<Integer> list = new ArrayList<>();
for(int i = 0; i < n; i++){
list.add(i);
}
//开始删除元素
//从m位置开始
int index = 0;
while(list.size() != 1){
index = (index + m - 1) % list.size();
list.remove(index);
}
return list.get(0);
}
}
由于ArrayList的remove()方法时间复杂度是O(n),删除了n-1次,所以整体时间复杂度是O(n²)。leetcode上通过时长1.33s,确实太久了...
二、数学方法解决约瑟夫环问题
约瑟夫环本是一圈人依次报数,到第m个人就会被杀。
假设使用数字来代替人:
0 1 2 3 4 5 6 7 8 9 10
这里设置m=3,从0开始,第3个人会被淘汰
第一轮:2被淘汰,2之后的人作为数组的新起点
3 4 5 6 7 8 9 10 0 1
第二轮:5被淘汰,5之后的人作为新起点
6 7 8 9 10 0 1 3 4
第三轮:8被淘汰,8之后的人作为新起点
9 10 0 1 3 4 6 7
这里可以逆推了:
第三轮之后,即将被淘汰的是9,9在第三轮之前的位置为:
(0+m) % 9 = 3
于是:
由于最终胜利者的位置是0,上一轮的位置要往后移动3位,但由于是圆圈循环,要模上一个人数得出胜利者的位置:
倒数第二轮:(0+3) % 2 = 1
倒数第三轮:(1+3) % 3 = 1
可得到递归公式:f(n,m) = (f(n-1,m) + m) % n
class Solution {
public int lastRemaining(int n, int m) {
int res = 0;
for(int i = 2; i <= n; i++){
res = (res + m - 1) % i + 1;
}
return res;
}
}