以下思考借鉴了该题的评论区,以及b站的讲解视频,做出分享。
这道题,是一道典型的约瑟夫问题。
对于约瑟夫问题,我们第一个思路,就是唤醒链表。创建一个node节点,然后从头开始指针遍历。
但是,如果按照这个思路发展的话,我们会惊讶的发现,超时了。。
因为当n很大的时候,我们需要不断地从链表头遍历到链表尾,效率极低。。。
我们需要对此进行改进。为此我们可以使用jdk提供的ArrayList
如下:
class Solution {
/*
思路:
1.创建一个链表或者数组
2.将n个数添加到链表里去
3.用一个数代表最初的下标id,在n>1条件满足的情况下不断进行(id+m-1)%n的操作,不断删除id下标对应的值,n--
4.最后剩下的第一个值就是
*/
public int lastRemaining(int n, int m) {
//创建一个数组
ArrayList<Integer> list=new ArrayList<>(n);
//添加n个元素
for (int i = 0; i < n; i++) {
list.add(i);
}
//创建id
int id=0;
//不断循环,同时踢出对应的元素
while (n > 1) {
id = (id + m - 1) % n;
list.remove(id);
n--;
}
//返回数组第一个元素作为结果
return list.get(0);
}
}
但是效率是在是不敢称赞。
我们要进一步的优化:
这就要使用到我们的数学归纳法了。
简单来说,思路和上面有些相反,我们这次求留下的数,因为最后一轮是两个数踢出一个,所以我们先求只有两个数的情况留下谁,然后在求出三个数的情况留下的数,再逐步求出n个数留下的数。。
class Solution {
public int lastRemaining(int n, int m) {
int ans = 0;
// 最后一轮剩下2个人,所以从2开始反推
for (int i = 2; i <= n; i++) {
ans = (ans + m) % i;
}
return ans;
}
}
效率十分明显的提升了。