美文网首页LeetCode算法
1872-石子游戏Ⅷ-优化DP

1872-石子游戏Ⅷ-优化DP

作者: 华雨欣 | 来源:发表于2021-05-23 16:04 被阅读0次

写在前面

这周周赛的最后一题,经典递推博弈论,但是没想出来,通过学习看懂了推理过程,还顺便学会了这种通过前缀的方式优化DP,收获良多。

题目

核心思路

通过理解题意,不难发现,当取走左边若干个石子后,对右边石子原来的分数是没有影响的,仍是前缀和,所以预处理一个前缀和是很显然的。

int[] sum = new int[n + 1];
for(int i = 0; i < n; i++) { 
  sum[i + 1] = sum[i] + stones[i];
}

游戏过程我们不妨先不考虑时间的要求,直接通过暴力模拟来解决。

暴力法

暴力法直接模拟游戏过程,需要注意每一轮得到的结果都是这一轮的玩家期望得分差值的最大值。如果当前已经取到第i (1 <= i <= n)块石子,那么这一轮可以取到的结果solve(i)就是从in中选择一个位置j,使得sum[j] - (下一轮对手的得分)最大,这里的sum[j]就是这一轮的得分,由于要保证双方均采用最优策略,下一轮对手也会选择最大的得分差值,所以相当于求解sum[j] - solve(j + 1)的最大值。

暴力法代码
class Solution {

    int n;
    int[] stones;
    int[] sum;

    public int stoneGameVIII(int[] stones) {
        n = stones.length;
        this.stones = stones;
        sum = new int[n + 1];
        
        for(int i = 0; i < n; i++) sum[i + 1] = sum[i] + stones[i];

        return solve(2);
    }

    public int solve(int idx){
        if(idx == n) return sum[idx];

        int res = sum[n];
        for(int i = idx; i < n; i++){
            res = Math.max(res, sum[i] - solve(i + 1));
        }
        return res;
    }
}

记忆化递归O(N ^ 2)

完全模拟达到指数级别的时间复杂度,肯定需要进行优化,递归加优化最常见的就是加一个备忘录,写成记忆化递归。

O(N ^ 2)递归代码
class Solution {

    int n;
    int[] stones;
    int[] sum;
    Integer[] memo;

    public int stoneGameVIII(int[] stones) {
        n = stones.length;
        this.stones = stones;
        
        memo = new Integer[n + 1];
        sum = new int[n + 1];

        for(int i = 0; i < n; i++) sum[i + 1] = sum[i] + stones[i];
        memo[n] = sum[n];
        return solve(2);
    }

    public int solve(int idx){
        if(memo[idx] != null) return memo[idx];

        int res = sum[n];
        for(int i = idx; i < n; i++){
            res = Math.max(res, sum[i] - solve(i + 1));
        }
        return memo[idx] = res;
    }
}

记忆化过程还是很简单的,直接加个备忘录就可以了,不过这样还是O(N ^ 2)的时间复杂度,还是会超时的。

前缀优化DP

在记忆化中,每次递归都要从当前位置向后遍历找到最大的满足条件的值,时间消耗较大,而每个位置都只与他后边的值有关,我们不妨来看一下solve(x)的值到底等于什么。

solve(x) = max(sum[x] - solve(x + 1), sum[x + 1] - solve(x + 2), ... , sum[n - 1] - solve(n), sum[n] - solve(n + 1))

而后边这一段sum[x + 1] - solve(x + 2), ... , sum[n - 1] - solve(n), sum[n] - solve(n + 1),恰好是solve(x + 1)的值,带入也就得到

solve(x) = Math.max(solve(x + 1), sum[x] - solve(x + 1))
这样我们就可以得到优化到O(N)时间复杂度的代码了

O(N)递归代码
class Solution {

    int n;
    int[] stones;
    int[] sum;
    Integer[] memo;

    public int stoneGameVIII(int[] stones) {
        n = stones.length;
        this.stones = stones;
        
        memo = new Integer[n + 1];
        sum = new int[n + 1];

        for(int i = 0; i < n; i++) sum[i + 1] = sum[i] + stones[i];
        memo[n] = sum[n];
        return solve(2);
    }

    public int solve(int idx){
        if(memo[idx] != null) return memo[idx];

        int res = Math.max(solve(idx + 1), sum[idx] - solve(idx + 1));
        return memo[idx] = res;
    }
}

当然递归可以完成,迭代也同样可以,不过迭代DP是自底向上求解,在这道题里也就是从dp[n]开始一直求到dp[2],逆序递推即可

O(N)动态规划代码
class Solution {
    public int stoneGameVIII(int[] stones) {
        int n = stones.length;
        int[] sum = new int[n + 1];
        for(int i = 0; i < n; i++){
            sum[i + 1] = sum[i] + stones[i];
        }

        int[] dp = new int[n + 1];
        dp[n] = sum[n];

        for(int i = n - 1; i >= 2; i--){
            dp[i] = Math.max(dp[i + 1], sum[i] - dp[i + 1]);
        }
        return dp[2];
    }
}

可以发现dp[i]只与dp[i + 1]有关,经典的空间优化,用一个变量代替dp数组即可

O(N)动态规划优化空间代码
class Solution {
    public int stoneGameVIII(int[] stones) {
        int n = stones.length;
        int[] sum = new int[n + 1];
        for(int i = 0; i < n; i++){
            sum[i + 1] = sum[i] + stones[i];
        }

        int res = sum[n];

        for(int i = n - 1; i >= 2; i--){
            res = Math.max(res, sum[i] - res);
        }
        return res;
    }
}

总结

博弈论的问题也做过几道了,还是不太能抓得住要领,不过这种优化DP的方法还是很值得学习的,希望可以越来越强。
如果文章有写的不对的地方,还请指出,感谢相遇~~

相关文章

  • 1872-石子游戏Ⅷ-优化DP

    写在前面 这周周赛的最后一题,经典递推博弈论,但是没想出来,通过学习看懂了推理过程,还顺便学会了这种通过前缀的方式...

  • 5498. 石子游戏 V

    5498. 石子游戏 V 区间dp 这一题引出了一个很好的思考为啥记忆化dfs比填表的dp快 因为填表dp是自底向...

  • 5627. 石子游戏 VII(区间dp)

    5627. 石子游戏 VII[https://leetcode-cn.com/problems/stone-gam...

  • 1563-石子游戏Ⅴ-区间DP问题

    题目 分析 题意还是比较好理解的,每次将石子分成两大堆,抛弃总和大的那一堆,留下少的一堆并且总分数中加上少的一堆的...

  • DP训练——斜率优化DP

    斜率优化DP 斜率优化DP涉及到的模型较多,在编写习题题解前,先做出如下规律总结。 如何识别斜率优化DP 按照正常...

  • lintcode 394+395 游戏博弈

    lintcode 394 解题思路: dpdp[i]:代表面对i个石子,是否先手必赢。dp[i]=(dp[i-1]...

  • 前缀和优化DP

    前缀和优化 DP 当 DP 转移方程是如下形式的时候 计算 dp[i] 时需要一步求和 sum(dp[a..b])...

  • 486预测赢家-877石子游戏(区间dp)

    这是一道区间dp的问题,我们可以先用递归的方法求解。 intchooseStart=nums[start]-dfs...

  • 「动态规划」例题之状态和转移方程的优化

    0x50「动态规划」例题 倍增优化DP 有些题目中,为了加速阶段的递推,我们会使用倍增去优化DP过程。通常情况下,...

  • DP训练——线段树优化DP

    线段树优化DP HDU3698题意给定的矩阵和,须从矩阵的每行选择一个数字,使得数字和最小。选择时须保证前后选择的...

网友评论

    本文标题:1872-石子游戏Ⅷ-优化DP

    本文链接:https://www.haomeiwen.com/subject/ogczjltx.html