0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
示例 1:
输入: n = 5, m = 3
输出: 3
示例 2:
输入: n = 10, m = 17
输出: 2
方法: 动态规划
定义状态方程: d p [ i ] dp[i] dp[i]表示,有 i i i个元素时,最后剩下人的索引。(状态定义为关键点)
- 首先假设有五个元素时已知最后留下的索引为3,如下图:
此时最后留下的元素索引为3
- 接下来,从其中第一次拿掉第3个数:(此时需要注意,拿掉一个元素后是需要从拿掉的那个元素下一个开始重新计数的,所以这时红色星星排第一了)
此时最后留下的元素索引为0
- 再拿掉第3个,之后从拿掉的下一个开始重新计数:
此时最后留下的元素索引为1
- 再拿掉第3个,从拿掉的下一个开始重新计数:
此时最后留下的元素索引为1
- 再拿掉第3个,从拿掉的下一个开始重新计数:
此时只有一个元素了,最后留下的元素索引为1
可得出:dp[1] = 1,dp[2] = 1,dp[3] = 1,dp[4] = 0,dp[5] = 3
那么是如何能从dp[i-1]得出dp[i]呢?
我们看dp[3]和dp[4],如下图:
由图可以看出,将下面的3个元素最后添加一个蓝色元素,在将他们依次向右移3位,就与上面的完全相同,因此可得出,红色五星序号的变换规律, dp[i] = (dp[i-1] + m) % i ,即状态转移方程。
因为是先添加元素,再右移,所以是对添加后的元素个数求余。
状态转移方程: dp[i] = (dp[i-1] + m) % i
初始状态: d p [ 0 ] dp[0] dp[0]无意义, d p [ 1 ] = 0 dp[1] = 0 dp[1]=0 。
返回值: d p [ ] dp[] dp[]最后一项
python3
class Solution:
def lastRemaining(self, n: int, m: int) -> int:
dp = [0]*(n+1) # 因为索引是从0开始的,但是存在意义的只有1个,2个...n个,所以要留n+1个空
dp[1] = 0
for i in range(2,n+1):
dp[i] = (dp[i-1]+m) % i
return dp[-1]
C++
class Solution {
public:
int lastRemaining(int n, int m) {
int dp[n+1];
dp[1] = 0;
for(int i=2; i<=n; i++){
dp[i] = (dp[i-1] + m) %i;
}
return dp[n];
}
};