leetcode
leetcode 1051 ~ 1100
石子游戏 II

石子游戏 II

难度:

标签:

题目描述

代码结果

运行时间: 93 ms, 内存: 16.4 MB


/*
 * 思路:使用动态规划来解决这个问题。dp[i][M]表示当剩下的石子堆为piles[i:]时,M的值为M时,当前玩家最多可以拿到的石子数量。
 * 我们从后向前计算,最终dp[0][1]就是我们要的答案。
 */
import java.util.stream.IntStream;

public class Solution {
    public int stoneGameII(int[] piles) {
        int n = piles.length;
        int[][] dp = new int[n][n + 1];
        int[] suffixSum = new int[n];
        suffixSum[n - 1] = piles[n - 1];
        IntStream.range(0, n - 1).forEach(i -> suffixSum[n - 2 - i] = suffixSum[n - 1 - i] + piles[n - 2 - i]);
        IntStream.range(0, n).forEach(i -> {
            IntStream.range(1, n + 1).forEach(M -> {
                if (i + 2 * M >= n) {
                    dp[n - 1 - i][M] = suffixSum[n - 1 - i];
                } else {
                    IntStream.range(1, 2 * M + 1).forEach(X -> {
                        dp[n - 1 - i][M] = Math.max(dp[n - 1 - i][M], suffixSum[n - 1 - i] - dp[n - 1 - i + X][Math.max(M, X)]);
                    });
                }
            });
        });
        return dp[0][1];
    }
}

解释

方法:

这个问题可以通过动态规划来解决。定义 dp[i][m] 为从第 i 堆开始,M 值为 m 时,当前玩家可以获得的最大石子数。s 用来记录从当前堆到最后一堆的石子总数(后缀和)。如果从第 i 堆开始,玩家可以选择拿 1 到 2m 堆的石子,那么 dp[i][m] 就应该是 s 减去后续玩家可能的最小值,即 s - min(dp[i+x][max(m,x)] for x in range(1, m*2+1))。如果 i + m * 2 超出了石子堆的数量,即 i+m*2 >= n,当前玩家可以直接拿走所有剩余的石子,dp[i][m] = s。最终解为 dp[0][1],即从第一堆开始,M=1 的情况。

时间复杂度:

O(n^2)

空间复杂度:

O(n^2)

代码细节讲解

🦆
题解中提到的动态规划数组`dp[i][m]`具体是怎样定义的?能否详细解释其代表的意义?
在这个问题中,`dp[i][m]`表示从第`i`堆石子开始,当`M`值为`m`时,当前玩家可以获得的最大石子数。这是一个两维动态规划数组,其中`i`代表起始堆的索引,`m`代表根据游戏规则中的`M`值,影响玩家可以选择拿走的堆数。`dp[i][m]`的优化目标是最大化从`i`堆开始时玩家的石子收益,考虑到对方玩家的最优策略。
🦆
在题解的动态规划过程中,为何要计算`s - min(dp[i+x][max(m,x)] for x in range(1, m*2+1))`,这样的计算逻辑是基于什么考虑?
这个计算逻辑基于博弈论中的`最优反应策略`。`s`代表从第`i`堆到最后一堆的石子总数。`s - min(dp[i+x][max(m,x)] for x in range(1, m*2+1))`的计算是为了确定在当前玩家拿走某数量石子后,对手能够获得的最小收益,从而使当前玩家的收益最大化。具体来说,对手在接下来的回合中,根据游戏规则选择`x`堆石子,使得`M`更新为`max(m, x)`,动态规划寻求在所有可能的选择中使对手的收益最小,从而确保当前玩家收益最大。
🦆
题解中更新后缀和`s`的代码是如何实现的?这个后缀和在整个动态规划中起到了什么作用?
在代码中,后缀和`s`是通过从数组的末尾开始向前累加实现的。这一过程在循环中`for i in range(n - 1, -1, -1)`逐步执行,每次循环中`s += piles[i]`更新当前的后缀和。后缀和`s`在动态规划中的作用是记录从当前堆`i`开始到最后一堆石子的总数,这是计算当前玩家在任何选择下都能保证的基础石子数,用于与对手可能的最小收益作比较,进而决定最优的拿取策略。
🦆
动态规划解法中提到当`i + m * 2 >= n`时,当前玩家可以直接拿走所有剩余的石子,为什么会有这样的结论?
这个结论基于游戏的规则和石子的分布。根据游戏规则,玩家每次可以选择拿取的石子堆数最多为`2m`。如果`i + m * 2 >= n`,意味着从堆`i`开始的剩余石子堆数不足`2m`堆,也就是说,当前玩家在此回合内可以选择拿走所有剩余的石子堆,因为其选择范围覆盖了所有剩余堆。因此,此时`dp[i][m]`就等于剩余石子的总和`s`。

相关问题