题目描述
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去....直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
问题核心:
我们看待这个问题,首先,第一轮时被淘汰的那个数字,肯定不会是最后剩下的那个数字。
因此,最后剩下的数字一定在n-1个数中。
而在这n-1个数中,这一轮淘汰的数字也不可能是最后剩下的数字,因此最后剩下的数字一定在n-2个数中。
从上我们就可以看出规律,n个数,每m个数淘汰一个数的约瑟夫环的问题,实际上就是递归求解(n-1,m)所剩下的那个数,也就是求解(n-2,m)剩下的那个数......
实际上这就是一个递归的过程。
而真正的关键点在于,我们求解得到的这个数,一定要是n个数时的索引,这是什么意思呢?
比如,n=10,m=4:
0 1 2 3 4 5 6 7 8 9
现在我们淘汰了(4-1)%10=3,变为了
0 1 2 4 5 6 7 8 9
相当于说求解4 5 6 7 8 9 0 1 2中所剩下的最后一个数,也就是(10-1,4)这个子问题。
但问题在于,求解(n-1,m)时所得到的最后一个数的索引并不是我们要求的索引。
比如在求解(9,4)这个问题,得到了结果是 2是最后一个被剩下的数字
也就是
0 1 2 3 4 5 6 7 8
但实际上我们所要得到的结果是2在(9,4)这个问题下的结果,而在(10,4)下面,结果应该是6
(10,4)下的坐标系: 4 5 6 7 8 9 0 1 2
(9,4)下的坐标系: 0 1 2 3 4 5 6 7 8
这就是问题的关键,(n-1,m)得到的结果,是在(n-1,m)这个坐标系下的索引,而我们最终要求的,是(n,m)下的索引,因此要将2转换到(n,m)坐标系下,也就是第三个数,就是4
这么说可能不好理解,我们具体到题目里:
如果10个小朋友,每4个出局一个。
现在第四个小朋友,也就是3出局。然后从4开始找,当我们从4开始找的时候,其实就是进入了子问题(n-1,m)。
在子问题中,第一个念的小朋友是4,但它其实是子问题里面第一个念的小朋友,也就是0。
假设这个游戏中最后一个小朋友刚好就是4这个小朋友,但在子问题(10-1,4)中,所得到的结果是0,也就是子问题坐标系下第一个小朋友。
但题目所求的结果是4,因此,我们要做的就是将0转换到4.
所以这个问题的实质,就是将(1,4)的结果转换到(2,4)坐标系下,在将(2,4)转换到(3,4).....直到(n,4)坐标系下,这才是我们的结果。
关于子问题间坐标系的转换,我们考虑已经淘汰3的情况下:
(n,m): 0 1 2 4 5 6 7 8 9
(n-1,m): 6 7 8 0 1 2 3 4 5
考虑(n,m),新坐标系的起点,一定是旧坐标系下的m位置,因为旧坐标系一定是从0开始数,淘汰m-1,然后将m作为子问题的起点。因此旧坐标系下的某点x转换到新坐标系下一定是x-4;当x-4小于0,代表从尾部开始数,比如新坐标系下的6,它是由0-4得到的,0-4表示从0开始往左移动4个,也就是从9 8 7 6,6就作为新坐标系的起点,因此实际上旧坐标系下x转换到新坐标系下就是(x-4+n)%n
而新坐标系下的点转换到旧坐标系下就是(y+4)%n
得到新坐标系下点到旧坐标系下点的转换方式我们就得到了解决的钥匙,
因为(1,m)所得的结果一定是0位置。
我们只要利用这个公式不断向上转换,比如(0+4)%2(这里的2是上一层的数的总数),所得的结果y继续向上转换:(y+4)%3.....直到转换到n
代码如下:
class Solution { public: int LastRemaining_Solution(int n, int m) { if(n<=0||m<=0) return -1; vector<int> dp(n); dp[0]=0; for(int i=1;i<dp.size();i++) { dp[i]=(dp[i-1]+m)%(i+1); } return dp.back(); } };