Dynamic Programming学习笔记 (41) - 石子游戏 (力扣# 877)

150 阅读2分钟

本题出自力扣题库第877题。题面大意如下:

甲乙两人用几堆石子在做游戏。一共有偶数堆石子,排成一行;每堆都有正整数颗石子,数目为 piles[i] 。 游戏以谁手中的石子最多来决出胜负。石子的总数是奇数,所以没有平局。 甲乙轮流进行,甲先开始 。 每回合,玩家从行的开始或结束处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止,此时手中石子最多的玩家获胜 。 假设甲乙都选择最佳策略,当甲赢时返回 true,当乙时返回 false 。

示例:

输入:piles = [5,3,4,5]
输出:true
解释:
甲先开始,取了前 5 颗,这一行就变成了 [3,4,5]。
如果乙拿走前 3 颗,那么剩下的是 [4,5],甲拿走后5颗赢得10分。
如果乙拿走后 5 颗,那么剩下的是 [3,4],甲拿走后4颗赢得9分。
这表明,取前5颗石子对甲来说是一个胜利的举动,所以返回 true 。

题解:

这个题目属于双人博弈类DP问题,对于此类问题使用递归法较为容易理解。所需要实现的递归方法就是符合游戏规定的最佳策略。根据题意,玩家在每一步都有两个选择,取第一堆,或是最后一堆,两者之间的最优选择就是当玩家取了一堆石头后,其获得的石头数与对家在下一步可能获得的石头数的差最大化。递归过程则代表双方轮流进行选择,递归的边界情况在于当只剩最后一堆石头时,那么当前玩家就只有一个选择以结束游戏,递归也因此结束。

Java代码如下:

class Solution {
    int[][] dp;
    public boolean stoneGame(int[] piles) {
        int N = piles.length;
        if (N <= 1) {
            return true;
        }

        dp = new int[N][N];
        return helper(0, N - 1, piles) > 0 ;
    }

    private int helper(int start, int end, int[] piles) {
        if (end == start) {
            return piles[start];
        }

        if (dp[start][end] != 0) {
            return dp[start][end];
        }

        int optionA = piles[start] - helper(start + 1, end, piles);
        int optionB = piles[end] - helper(start, end - 1, piles);

        return dp[start][end] = Math.max(optionA, optionB);
    }
}

本题也可以通过数学方法证明先手的甲总是会赢。