题目链接:1140. 石子游戏 II(中等)
算法原理:
解法:DFS
此时未做优化,会超时
时间复杂度O(N³)
①由于我们是从前往后轮流拿石子堆,到最后要是能全拿,咱就全拿,但我们不可能在全拿的时候重新遍历计算,所以此时需要用到后缀和
si:从 i 开始到末尾的后缀和(包括 pliesi)
②dfs 获取当前选手从 i 开始能拿到的最大石子数,由于 Alice 先手,所以直接返回 dfs(0,1)即可
定义:dfs(i,m,s):当前选手从 i 开始在M=m时能拿到的最大石子数
③当 i+2m≥全部堆数时,说明能直接全拿走,直接返回 si
④由于我能获取到的最大石子数=总数-对手能拿到的最小石子数,所以我们要先计算出对手能获取到的最大石子数的最小值(因为我要收益最大化)
5️⃣遍历我拿的堆数,从而计算出对手拿的堆数
为什么是Math.max(m,x)而不是x?
因为M只能变大,不能变小,在后续过程中M变大之后,再次重新循环的时候如果还是用x,那x会从1开始遍历,但实际上M此时已经很大了,不符合游戏逻辑
记忆化搜索优化
3ms击败84.35%
时间复杂度O(N³)
当 Alice 拿一堆,Bob 拿两堆 和 Alice 拿两堆,Bob 拿一堆时,都会用到 dfs(3,2),所以上述这个过程会产生大量的重复递归,所以我们可以用一个备忘录 memo 数组来存储第一次算出结果时的结果,从而避免大量的重复递归
但如果要用二维数组记录的话,第二个纬度要开多大空间比较合适??再或者说,M的最大上限是多少??(这里借助了灵神的思路~~)
两人交替拿石子,为了在 i 尽量小的情况下,使 M 尽量大,那么每次都拿满 2M 堆,最后会有:
(2+4+8+......+M)+2M<n,即 4M-2<n,亦为 4M≤n+1,算出最大上界为
Java代码:
java
class Solution {
//1140. 石子游戏 II
//解法:DFS(未优化,会超时)
public int stoneGameII(int[] piles) {
//计算后缀和,s[i]:包括 plies[i]的后缀和
int[] s=piles;
int n=piles.length;
for(int i=n-2;i>=0;i--) s[i]+=s[i+1];
//Alice 先手,dfs结果即返回值
return dfs(0,1,s);
}
//dfs(i,m,s):当前选手从 i 开始能拿到的最大石子
private int dfs(int i,int m,int[] s){
//能全拿走(s[i]是剩下全部石子的总和)
if(i+m*2>=s.length) return s[i];
//mn:对手能拿走的最大收益的最小值
int mn=0x3f3f3f3f;
for(int x=1;x<=m*2;x++){
//这里的dfs从i+1开始,代表对手的最大收益
//为啥是Math.max(m,x)而不是x?
//因为M只能变大,不能变小,在后续过程中M变大之后,再次重新循环的时候如果还是用x,那x会从1开始遍历,但实际上M此时已经很大了,不符合游戏逻辑
mn=Math.min(mn,dfs(i+x,Math.max(m,x),s));
}
//抛去对手的,就是自己的
return s[i]-mn;
}
}
java
class Solution {
//1140. 石子游戏 II
//解法优化:记忆化搜索
public int stoneGameII(int[] piles) {
//计算后缀和,s[i]:包括 plies[i]的后缀和
int[] s=piles;
int n=piles.length;
for(int i=n-2;i>=0;i--) s[i]+=s[i+1];
int[][] memo=new int[n-1][(n+1)/4+1];
for(int[] row:memo) Arrays.fill(row,-1);//-1表示没有计算过
//Alice 先手,dfs结果即返回值
return dfs(0,1,s,memo);
}
//dfs(i,m,s):当前选手从 i 开始能拿到的最大石子
private int dfs(int i,int m,int[] s,int[][] memo){
//能全拿走(s[i]是剩下全部石子的总和)
if(i+m*2>=s.length) return s[i];
//之前计算过
if(memo[i][m]!=-1) return memo[i][m];
//mn:对手能拿走的最大收益的最小值
int mn=0x3f3f3f3f;
for(int x=1;x<=m*2;x++){
//这里的dfs从i+1开始,代表对手的最大收益
//为啥是Math.max(m,x)而不是x?
//因为M只能变大,不能变小,在后续过程中M变大之后,再次重新循环的时候如果还是用x,那x会从1开始遍历,但实际上M此时已经很大了,不符合游戏逻辑
mn=Math.min(mn,dfs(i+x,Math.max(m,x),s,memo));
}
//抛去对手的,就是自己的
return memo[i][m]=s[i]-mn;//记忆化
}
}