经典的约瑟夫环问题。
法一:暴力模拟
class Solution {
private ListNode head = new ListNode(-1);
private ListNode tail;
public int lastRemaining(int n, int m) {
if(n == 1)return 0;
constructCycle(n);
ListNode cur = tail;
int left = n;
while(left > 1){
int step = m;
for(int i = 0; i < step-1; i ++)cur= cur.next;
cur.next = cur.next.next;
left --;
//System.out.println(cur.val);
//System.out.println(cur.next.val);
}
return cur.val;
}
private void constructCycle(int n){
ListNode cur = head;
for(int i = 0; i < n; i ++){
ListNode node = new ListNode(i);
cur.next = node;
cur = cur.next;
}
cur.next = head.next;
tail = cur;
}
private class ListNode {
int val;
ListNode next;
ListNode(int val){
this.val = val;
this.next = null;
}
}
}
法二:递推
考虑减小问题的规模,对于一个规模为 n 的问题,删掉的第一个节点编号是 m%n - 1, 设 k = m % n - 1. 则剩下的情形变成一个规模为 n - 1 的问题, 其中编号有映射关系
原问题中 k ~ n-1 $\to$ 子问题中 0 ~ n - k -1, 原问题中0 ~ k - 2 $\to$ 新问题中 n - k ~ n -2
根据逆映射,我们可以建立递推方程:
f[n] = (f[n-1] + k) % n
f[1] = 0
class Solution {
private int[] f;
public int lastRemaining(int n, int m) {
f = new int[n + 1];
f[1] = 0;
for(int i = 2; i <= n; i ++){
f[i] = (f[i-1] + m % i) % i;
}
return f[n];
}
}