390. 消除游戏
class Solution {
//网上借鉴,作者思路真的太好了
/*
1.首先这个题目不可能用模拟的方法计算。因为最大的示例超过100000000,需要进行99999999次删减,时间上绝对来不及。
2.遇到这种时间明显不够的题目,第一想法当然是用动态规划来做。
2.1 先从最简单的情况入手:
n=1时,答案为1。
n=2时,答案为2。
。。。。。。
2.2 可以发现,答案一定不会是奇数,因为第一轮操作一定会将所有的奇数删除。这就提示出一个规律:
如果n为奇数,那么以n结尾和以n-1结尾是完全一样的。
例如1,2,3,4,5,6,7,8,9,操作一轮后剩2,4,6,8,下一轮从8开始;
而1,2,3,4,5,6,7,8,操作一轮后也剩2,4,6,8,下一轮也从8开始。
从而得到第一个递推公式:当n为奇数时,dp[n] = dp[n-1];
2.3 接下来就只剩n为偶数的情况了。
仍以1,2,3,4,5,6,7,8为例,操作一轮后剩2,4,6,8, 下一轮从8开始。
那么2,4,6,8从8开始,和2,4,6,8从2开始有什么区别呢?
很明显就是轴对称的关系,前者剩6,后者就剩(8+2 - 6);
那么2,4,6,8从2开始,和1,2,3,4从1开始有什么区别呢?
这个关系更明显,就是2倍的关系。
写到这里,大家应该明白了,1,2,3,4从1开始不就是dp[4]吗!
也就是说,dp[8] = 2*(1+4-dp[4]); //这里的1+4-dp[4]起的就是轴对称的作用。
推而广之,n为偶数时,dp[n] = 2*(1+n/2-dp[n/2])。
这样完整的递推公式就完成了:
n为奇数时,dp[n] = dp[n-1];
n为偶数时,dp[n] = 2(1+n/2-dp[n/2])。
3.本来以为这里就做完了,然而发现了一个很尴尬的事情,n=100000000时,dp数组爆内存了。不过递推公式都有了,按照相同思路写个递归程序也不在话下了:
作者:lch-22
链接:https://leetcode-cn.com/problems/elimination-game/solution/cdong-tai-gui-hua-yu-di-gui-si-lu-by-lch-22/
*/
public int lastRemaining(int n) {
return cal(n);
}
public int cal(int n){
if(n==1) return 1;
if(n==2) return 2;
if(n%2==0){
//return cal(n)=2*(1+n/2-cal(n/2));
return 2*(1+n/2-cal(n/2));
}else{
return cal(n-1);
// return cal(n)=cal(n-1);
}
}
}