leetcode
leetcode 751 ~ 800
石子游戏

石子游戏

难度:

标签:

题目描述

代码结果

运行时间: 1004 ms, 内存: 173.8 MB


// Java Stream solution for the problem
// 思路: 我们可以使用动态规划的思想,通过Java Stream的特性简化代码。每次迭代计算出每个区间的最大得分,然后使用流处理的方式更新。
import java.util.stream.IntStream;

public class StoneGame {
    public boolean stoneGame(int[] piles) {
        int n = piles.length;
        int[][] dp = IntStream.range(0, n).mapToObj(i -> new int[n]).toArray(int[][]::new);
        IntStream.range(0, n).forEach(i -> dp[i][i] = piles[i]); // 初始化
        IntStream.range(2, n + 1).forEach(l -> // l是区间长度
            IntStream.range(0, n - l + 1).forEach(i -> {
                int j = i + l - 1;
                dp[i][j] = Math.max(piles[i] - dp[i + 1][j], piles[j] - dp[i][j - 1]);
            })
        );
        return dp[0][n - 1] > 0; // 判断先手是否能赢
    }
}

解释

方法:

这个题解使用了动态规划的思路。定义了一个二维的备忘录memo,用于记录区间 [i, j] 内先手和后手最多能拿到的石子数。 在 dp 函数中,对区间 [i, j] 进行划分,如果区间长度为 1,直接返回该堆石子的数量。否则,分别计算先手拿左边或右边的石子堆时,对手能拿到的最大数量。最后返回两种情况下,先手能拿到石子数更多的那个。 在主函数中,调用 dp(0, n-1),返回先手和后手拿到的石子数,比较大小即可判断先手是否获胜。

时间复杂度:

O(n^2)

空间复杂度:

O(n^2)

代码细节讲解

🦆
在动态规划问题中,为什么选择使用一个二维备忘录memo来存储每个子问题的结果,而不是其他数据结构如数组?
在解决这个问题时,我们需要存储每一对索引(i, j)的结果,这对应于每一个子问题的解。使用二维备忘录(如字典)使得我们可以灵活地存储和查询任意(i, j)对的结果,而不必为每一对可能的索引组合预先分配固定的空间。这种方式在处理稀疏数据或者不连续的索引时特别有效,并且可以节省内存。如果使用数组,我们需要预先定义一个足够大的二维数组来存储所有可能的索引组合,这可能会导致空间的浪费,特别是当索引的可能组合并不是全部需要被使用时。
🦆
细节问题:在dp函数中,当区间长度为1时,为什么返回的元组中第二个元素是0?这是否意味着只有一个玩家可以拿石子?
当区间长度为1时,即只有一堆石子,先手可以拿走这堆石子,因为之后没有剩余的石子可供后手选,所以后手无法拿到任何石子。因此,在这种情况下返回的元组中第二个元素是0,确实表示在这个特定的子问题中,后手没有石子可拿。这不是说只有一个玩家可以拿石子,而是在这种特定的局面下,后手无石子可拿。
🦆
在决定先手拿左边还是右边的石子时,如何确定哪种选择能够确保先手最终拿到的石子数最多?
在决策过程中,我们计算两种情况:先手拿左边的石子或右边的石子。对于每种情况,我们都会计算在先手拿走石子后,后手所能拿到的最大石子数。然后,我们会把后手的最大值加到当前先手拿的石子数上,得到先手在这两种决策下的总石子数。最后,我们比较这两种情况下的先手总石子数,并选择先手石子数更多的那种情况。这种方法确保了先手可以在当前局面下最大化其拿到的石子总数。
🦆
题解中提到,比较dp(0, n-1)返回的先手和后手的石子数以判断先手是否获胜,是否有更直接的方法来预测游戏的结果?
在这个特定的石子游戏中,因为先手总是可以选择对其最有利的策略,实际上先手总是处于有利位置。因此,可以更直接地预测游戏结果:无论如何,先手总是会赢。这是因为先手可以根据堆中石子的总量和分布,制定拿石子的策略以确保总是处于领先。但在代码实现中,我们通常通过动态规划来验证这一点,以确保考虑所有可能的情况和策略。

相关问题