先容我吐槽一下,这是哪门子的简单题,救命!
最直接的思路:模拟!
既然是要删除圆圈中的数字,那么我们可以用环形链表来模拟,虽然说是用环形链表来模拟,我们不需要严格来写,只要保证指针能玩的转,都可以看作环形链表。下面我以deque
来实现,实现完了发现不用deque
,哪怕用vector
都可以实现:
class Solution {
public:
int lastRemaining(int n, int m) {
deque<int> circle;
for(int i=0;i<n;i++)
circle.push_back(i);
// i用来记录索引,cnt用来数数
int cnt=1,i=0;
while(circle.size()>1){
if(cnt==m&&i<circle.size()){
circle.erase(circle.begin()+i);
//erase以后指针有变化,要--
i--;
cnt=0;
}
else if(cnt==m){
i=i%circle.size();
circle.erase(circle.begin()+i);
i--;
cnt=0;
}
else{
cnt++;
i++;
}
}
return circle[0];
}
};
这里要非常注意erase
的用法。按照STL中的说明,这种erase
会改变iterator,除非是在开头和结尾的erase,所以为了不引起混乱,我们要清楚地更新i
和cnt
。cnt
很好理解,每次从1数到m就归零。i
就需要注意,当每次有erase
的操作,i
需要回退一位。当i
已经过了当前最大的位置时需要取余。需要注意,当circle.begin()
所指的位置被erase
后,circle.begin()
会指向下一个位置。
数学推导
模拟的思路很明确,但是过不了关,会超时。这样我们不得不思考更好的方法。在《剑指Offer》中提供的思路就很好,我附上自己的理解:
我们对f(n,m)
的定义是整个序列,而我们最终需要的是剩下的那一个数,在所有序列中都存在的数字就是我们找的剩下最后一个数字,我们可以把上面总结的规律换成一个递归式: \(f(n,m)=\begin{cases}0&n=1\\\left[f(n-1,m)+m\right]\%n&n>1\end{cases}\) 接下来实现就非常简单了,放一个不用动脑的版本:
class Solution {
public:
int lastRemaining(int n, int m) {
if(n==1)
return 0;
return (lastRemaining(n-1,m)+m)%n;
}
};