292_Nim Game
一、题目详情
题目的意思是给定石头的数量n,双方每次只能拿1-3个石头,己方先手,问在给定n后,自己能不能获胜。
二、解题方法
第一种方法(Time Limit Exceeded)
一开始拿到题目,我的第一想法是使用博弈树的思想解题,当节点状态是己方行动&&石子数量<=3
获胜,当节点是对方行动&&石子数量<=3
失败。因为双方都足够聪明,所以要在对手每一步取走1个,2个,3个石子都必须取胜。代码如下:
class Solution {
public boolean canWinNim(int n) {
return canWinNimHelper(n,true);
}
//isSelf==true表示己方行动,false表示对手行动
public boolean canWinNimHelper(int n,boolean isSelf){
if(n<=3&&isSelf) return true;
if(n<=3&&!isSelf) return false;
if(isSelf)
return canWinNimHelper(n-1,!isSelf)
|| canWinNimHelper(n-2,!isSelf)
|| canWinNimHelper(n-3,!isSelf);
else
return canWinNimHelper(n-1,!isSelf)
&& canWinNimHelper(n-2,!isSelf)
&& canWinNimHelper(n-3,!isSelf);
}
}
不出意料这种方法Time Limit Exceeded.
第二种方法(Memory Limit Exceeded)
因为上面这种算法超时,我观察了一下该算法,可以使用动态规划的思想解决超时问题。算法具体为:现在求得是由n个石子是否能赢,则只要拿走1个棋子或者两个棋子或者三个棋子后,对方会输就行(己方和对方是一样的,dp[i]只表示现在行动的那方能不能赢)。所以就有:dp[i] = !(dp[i-1]&&dp[i-2]&&dp[i-3])
class Solution {
public boolean canWinNim(int n) {
if(n<=3) return true;
boolean[] dp = new boolean[n+1];
dp[1] = true;dp[2]=true;dp[3]=true;
for(int i=4;i<=n;i++){
dp[i] = !(dp[i-1]&&dp[i-2]&&dp[i-3]);
}
return dp[n];
}
}
这个我以为可以accept,但是题目给的数据太大,建立dp数组太耗空间,但是这里可以改进一下:因为dp时只需要i的i-1,i-2,i-3的值,所以可以只保留这三个,这样就解决了内存的问题。
第三种方法(Accept)
这种方法比较特殊。只要n不能被4整除自己就能赢。 解释:我们可以知道当n=1,2,3时我们可以赢,当n=4时我们会输。当n=5,6,7时,我们可以对应的拿掉1,2,3个石子让对方行动时只有4个棋子,我们就能赢。n=8时,我们会输。以此类推,只要n不能被4整除自己就能赢。class Solution {
public boolean canWinNim(int n) {
return !(n%4==0);
}
}