class Solution {
public int lastRemaining(int n, int m) {
// int x = 0;
// for(int i = 2; i <= n; i++){
// x = (x + m) % i;
// }
// return x;
//链表解法
List<Integer> list = new ArrayList<>();
for(int i = 0; i < n; i++){
list.add(i);
}
int index = 0;
while(n > 1){
index = (index + m - 1) % n;
list.remove(index);
n--;
}
return list.get(0);
}
}
class Solution {
public int lastRemaining(int n, int m) {
ArrayList<Integer> list = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
list.add(i);
}
int idx = 0;
while (n > 1) {
idx = (idx + m - 1) % n;
list.remove(idx);
n--;
}
return list.get(0);
}
}
//递归解法
func lastRemaining(n int, m int) int {
if n == 1 {
return 0
}
return (lastRemaining(n-1, m) + m) % n
}
//迭代解法
func lastRemaining(n int, m int) int {
dp := 0 //题目从数字零开始
for i := 2; i <= n; i++ {
dp = (dp + m) % i
}
return dp
}
class Solution {
public int lastRemaining(int n, int m) {
//f(n,m)和 f(n-1,m)之间的关系
if (n == 0) return n;
int res = 0;
for (int i = 1; i <= n; i++) {
res = (res + m) % i;
}
return res;
}
}
/**
用list模拟的好处在于可以动态的更新数组长度,下标对应变化
*/
class Solution {
public int lastRemaining(int n, int m) {
ArrayList<Integer> list = new ArrayList<>();
for(int i = 0;i < n;i++) {
list.add(i);
}
int idx = 0;
while(n > 1) {
idx = (idx + m - 1) % n;
list.remove(idx);
n--;
}
return list.get(0);
}
}
/**
当前轮的下标=(上一轮的下标+偏移位数)%上一轮的个数
*/
class Solution {
public int lastRemaining(int n, int m) {
int idx = 0;
for(int i = 2; i <= n;i++) {
idx = (idx + m) % i;
}
return idx;
}
}
// 时间复杂度O(n),空间复杂度O(1)
class Solution {
public int lastRemaining(int n, int m) {
int res = 0;
for (int i = 2; i <= n; i++) {
res = (res + m) % i;
}
return res;
}
}
共以下 10 个回答
链表解法更容易懂一些
数学公式,只会背过…..
模拟链表
时间复杂度O(N^2)空间复杂度O(N)
注意:如果使用LinkedList会超时,LinkedLIst删除指定节点的时间复杂度为
O(1),但是在remove的时间复杂度仍然是O(n),因为需要从头遍历到需要删除的位置。ArrayList索引到需要删除的位置为O(1),但是删除是O(N)ArrayList 的 remove 操作在后续移位的时候,其实是内存连续空间的拷贝的!所以相比于LinkedList大量非连续性地址访问,ArrayList的性能是很好的
每次删除的时间复杂度是 O(n),删除了 n-1 次,所以整体时间复杂度是 O(n^2)
数学解法
时间复杂度O(N)
这么著名的约瑟夫环问题,是有数学解法的!
因为数据是放在数组里,所以我在数组后面加上了数组的复制,以体现是环状的。我们先忽略图片里的箭头:
图中的绿色的线指的是新的一轮的开头是怎么指定的,每次都是固定地向前移位 m个位置。
然后我们从最后剩下的 3 倒着看,我们可以反向推出这个数字在之前每个轮次的位置。
最后的3下标是0
第四轮反推,补上m个位置,然后模上当时的数组大小2,位置是
(0+3)%2=1第三轮
(1+3)%3=1第二轮
(1+3)%4=0第一轮
(0+3)%5=3所以最终剩下的数字的下标就是3。因为数组是从0开始的,所以最终的答案就是3。
总结一下反推的过程,就是 (当前index + m) % 上一轮剩余数字的个数。
时间复杂度O(n),空间复杂度O(1)