1690. 石子游戏 VII:动态规划思考过程

题目描述

石子游戏中,爱丽丝和鲍勃轮流进行自己的回合,爱丽丝先开始 。
 
n块石子排成一排。每个玩家的回合中,可以从行中 移除 最左边的石头或最右边的石头,并获得与该行中剩余石头值之 和 相等的得分。当没有石头可移除时,得分较高者获胜。
 
鲍勃发现他总是输掉游戏(可怜的鲍勃,他总是输),所以他决定尽力 减小得分的差值 。爱丽丝的目标是最大限度地 扩大得分的差值 。
 
给你一个整数数组 stones ,其中 stones[i] 表示 从左边开始 的第i个石头的值,如果爱丽丝和鲍勃都 发挥出最佳水平 ,请返回他们 得分的差值 。

思路分析

  1. 首先肯定是看懂题目,当对题目信息完全了解之后,才能更好的更快速的解决它,否则很容易出错。本题目中,有一句话比较难理解——鲍勃发现他总是输掉游戏

解读:对于这种关乎答案的问题最好的理解方法就是举例子。
 

stones = [5,3,1,4,2]

假设爱丽丝先拿5: 得分score = 3 + 1 + 4 + 2
后一步鲍勃拿3: 其得分score = 1 + 4 + 2。 拿2:则 score = 3 + 1 + 4
 
可以看到一人一次的选择下,爱丽丝先选的这次得分一定比鲍勃后选的这次得分高。因为爱丽丝的得分包含了鲍勃的得分以及鲍勃移除的分数
 
即谁先选择谁赢。

  1. 分析什么是发挥出最佳水平
    • 有唯一最优解(这个时候就要想动态规划了,不过也可以先往后想)
    • 对爱丽丝自己来说,自己的最佳水平,应该是当自己选择时,让自己得分-鲍勃得分 最大。
    • 每个人每次有两种移除选择,我们要比较这两次的结果,选择一个对自己最佳的选择。 同时这个最佳选择,一方面于当前选择的得分有关,一方面也与后面的选择有关,所以自然可以想到递归搜索。
    • 想到搜索,就要想需不需要记忆化搜索,举个例子就可以明白需要记忆化。
    • 那么这个时候就有两种方法,记忆化回溯,或 动态规划,这两种方法等价,只是实现方式不一样。前者,自顶向下,后者,自底向上。
  2. 动态规划或记忆化搜索,以动态规划举例(两者思考过程其实一样)
    • dp矩阵的定义:
      • 一维 or 二维: 取首部或尾部,则矩阵前后都会变化,所以应该二维矩阵。
      • 定义:定义关注的是当前的含义——dp[i][j]指的是自己当前选择之后,自己的分数-别人的分数最大。
    • 递推关系:前面说了当前的选择与两部分有关,当前选择的得分,后续的最大得分差值。因为是轮流选择,所以后续的最大得分差值是相对于另一人的,所以两者是减法的关系。
      //获取当前选择的得分。
      int n = stones.size();
      vector<int> sum(n+1);
      /**
      这一块有一个数组定义的问题,这一块是sum[i]  表示 0_i-1的和
      为什么不能表示为0_i的和,
      	通常情况下应该都可以,只需要在处理前定义好,后续按照这个处理即可。
      本题中需要往前收缩,后续会处理sum[j-1] 所以会出现数组越界的问题。
      不过后面再改也行。这个不是很重要。
      **/
      for (int i = 0; i < n; i++) {
          sum[i + 1] = sum[i] + stones[i];
      }
      // dp[i][j] 选择i时,当前得分为 sum[j+1] - sum[i+1]
      // 选择j时,当前得分为 sum[j] - sum[i]	
      
      //递推公式
      dp[i][j] = max(sum[j + 1] - sum[i + 1] - dp[i + 1][j], 
                  sum[j] - sum[i] - dp[i][j - 1]);
      
    • 遍历顺序:看递推公式,dp[i][j]dp[i + 1][j]dp[i][j - 1]有关,所以在处理i之前,i+1应该已经处理完了,所以i为递减,同理,j为递加。

完整代码

动态规划

class Solution {
public:
    int stoneGameVII(vector<int>& stones) {
        int n = stones.size();
        vector<int> sum(n + 1);
        vector<vector<int>> dp(n, vector<int>(n, 0));

        for (int i = 0; i < n; i++) {
            sum[i + 1] = sum[i] + stones[i];
        }
        for (int i = n - 2; i >= 0; i--) {
            for (int j = i + 1; j < n; j++) {
                dp[i][j] = max(sum[j + 1] - sum[i + 1] - dp[i + 1][j], 
                sum[j] - sum[i] - dp[i][j - 1]);
            }
        }
        return dp[0][n - 1];
    }
};

记忆化搜索

//来自于力扣官方
class Solution {
public:
    int stoneGameVII(vector<int>& stones) {
        int n = stones.size();
        vector<int> sum(n + 1);
        vector<vector<int>> memo(n, vector<int>(n, 0));
        for (int i = 0; i < n; i++) {
            sum[i + 1] = sum[i] + stones[i];
        }

        function<int(int, int)> dfs = [&](int i, int j) -> int {
            if (i >= j) {
                return 0;
            }
            if (memo[i][j] != 0) {
                return memo[i][j];
            }
            int res = max(sum[j + 1] - sum[i + 1] - dfs(i + 1, j), sum[j] - sum[i] - dfs(i, j - 1));
            memo[i][j] = res;
            return res;
        };
        return dfs(0, n - 1);
    }
};

总结与思考

  1. 在进行问题思考的时候,应该先看清并读懂题目,再进行下一步工作。
  2. 善于总结,比如在遇到最优化问题的时候就应该思考能不能使用动态规划。
  3. 本文dp矩阵定义是一个难点,通过读懂题目才能想到该定义。
  • 9
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值