【LeetCode 每日一题】1140. 石子游戏 II(medium)

1140. 石子游戏 II

先贴一个保姆级题解here
看到题目想了15分钟,DP和DFS都想到了,还写了几行DFS,发现总是无法同时妥善处理“轮次”和“每次拿的数量”这两个因素。还是做的题太少了,从来没遇到过倒着推的题目。被分治的思想限制死了,总觉得得先解决子问题,从前往后推。然而实际上,是不可能不考虑后面的,因为题设是两人能看到后面的石子且每次都是完美发挥,所以如果从前面开始,需要考虑的东西就太多了。看了题解茅塞顿开,还能这么玩儿。

分析

前面说到我们没法不考虑后面,把前面的一部分当作子问题去递推,那如果反过来呢?
反过来就对了,我们倒着来,不考虑前面怎么拿,假设前面都是最优的,只考虑剩下的一部分。
假设剩下的石子数是 left_num=sum[i, n-1],M 保持题设,当前位置为 i

  • 当 left_num<=2M 时,那爱丽丝先手直接全拿了;
  • 当 left_num>2M 时,爱丽丝没法一次拿完,那她该怎么拿呢?这个时候我们需要考虑的是对手最优会怎么拿,然后爱丽丝拿剩下的。那对手最优会怎么拿呢?对手的最优拿法,和爱丽丝在相同境遇下的最优拿法是一样的,这样就消除了轮次问题!!! 这时,我们无论是dp还是dfs,都可以两个参数解决问题,即 dp[i][m]、dfs(i,m),i为当前位置。

动态规划

class Solution {
public:
    int stoneGameII(vector<int>& piles) {
        int n=piles.size();
        int dp[n][n+1];
        memset(dp,-1,sizeof(dp));
        int sum=0;
        for(int i=n-1;i>=0;i--){
            sum+=piles[i];//后缀和
            for(int m=1;m<=n;m++){
                if(i+2*m>=n){
                    dp[i][m]=sum;
                }
                else{
                    for(int x=1;x<=2*m;x++){
                        dp[i][m]=max(dp[i][m],sum-dp[i+x][max(m,x)]);
                    }
                }
            }
        }
        return dp[0][1];
    }
};

DFS

看懂了dp,自然也能写出DFS来了。但是众所周知,dp是优于dfs的(狗头),所以这题暴力会超时(题解说的,没试过),得用记忆化搜索。
怎么个记忆法呢,举个简单例子:假设当前只有三堆,那爱丽丝第一次无论是拿两堆还是一堆,都是dp[3][2],不会产生额外的分支,但是 dfs(3,2) 可以由上述的两种不同情况分别到达,每种情况都还要往下递归一次,所以我们需要用一个类似dp的数组来记录这个状态,第二次递归到 dfs(3,2) 的时候直接返回数组里面的结果,避免重复递归。

class Solution {
public:
    int n;
    int vis[105][105];
    int sum[105];//后缀和
    int dfs(int index, int m){
        if(index+m*2>=n) return sum[index];
        if(vis[index][m]!=-1) return vis[index][m];
        int max_alice=0;
        for(int x=1;x<=m*2;x++){
            max_alice=max(max_alice,sum[index]-dfs(index+x,max(m,x)));
        }
        vis[index][m]=max_alice;
        return max_alice;
    }
    int stoneGameII(vector<int>& piles) {
        n=piles.size();
        memset(vis,-1,sizeof(vis));
        sum[n-1]=piles[n-1];
        for(int i=n-2;i>=0;i--){
            sum[i]+=sum[i+1]+piles[i];
        }
        return dfs(0,1);
    }
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值