leetcode:石子游戏II

题目来源:力扣

题目介绍:

亚历克斯和李继续他们的石子游戏。许多堆石子 排成一行,每堆都有正整数颗石子 piles[i]。游戏以谁手中的石子最多来决出胜负。
亚历克斯和李轮流进行,亚历克斯先开始。最初,M = 1。
在每个玩家的回合中,该玩家可以拿走剩下的 前 X 堆的所有石子,其中 1 <= X <= 2M。然后,令 M = max(M, X)。
游戏一直持续到所有石子都被拿走。
假设亚历克斯和李都发挥出最佳水平,返回亚历克斯可以得到的最大数量的石头。
============================================================
示例:
输入:piles = [2,7,9,4,4]
输出:10
解释:
如果亚历克斯在开始时拿走一堆石子,李拿走两堆,接着亚历克斯也拿走两堆。在这种情况下,亚历克斯可以拿到 2 + 4 + 4 = 10 颗石子。
如果亚历克斯在开始时拿走两堆石子,那么李就可以拿走剩下全部三堆石子。在这种情况下,亚历克斯可以拿到 2 + 7 = 9 颗石子。
所以我们返回更大的 10。
====================================================

审题:

本题属于双人博弈问题,对于双人博弈问题,如果可以使用动态规划方法解决,我们可以添加状态变量表示当前正在参与游戏的对象, 这样处理方式在博弈问题中是通用的.大家可以尝试一下.如石子游戏这篇文章中的方法类似.

对于该问题,分析其最优子结构.假设当前剩余石子为ii堆,当前用户可以选择xx堆,1x2M1 \leq x \leq 2M.为了确定用户当前所做的最优选择XbestX_{best},我们需要知道用户在剩余ixi-x堆后最优方案下所能取得的最优方案.对于每一xx, 1x2M1\leq x \leq 2M, 如果我们知道了用户在ixi-x堆时的最优方案,则我们可以确定当前用户的最优选择XbestX_{best}.因此,该问题存在最优子结构.若当前问题为剩余石子为ii堆时的最优方案,其子问题为所有剩余堆数小于ii堆时的最优方案. 基于以上最优子结构,不能得出,其子问题存在重叠.因此我们可以考虑使用动态规划方法进行解决.

  • 确定状态变量
    该问题中最容易分析的一个状态量是当前石堆的状态.由于每次均从起始端一端选择,因此我们可是使用当前剩余石堆起始堆编号来描述当前石堆状态(也可使用剩余石堆数来描述当前状态).由于用户每一次能够选择的石堆数并不是固定的取值范围,取决于上一次的选择.因此我们需要一个状态量来描述当前其可选的石堆数,这个量就是题目中的M.最后,对于双人博弈问题,我们需要一个状态量来区分正在参与游戏的选手与处于等待状态的选手.

    石堆状态变量ii的可选范围为1ipiles.length11 \leq i \leq piles.length-1;状态变量MM的取值范围与当前石堆状态变量ii相关.如果前面每一用户每一步都选择1个石堆,则MM的最小值是1,如果前一用户一次性拿走了ii堆石子,则当前M的最大值为ii, 因此状态变量MM的取值范围为:1Mi1 \leq M \leq i.最后一个状态量取值为0,10, 1. 因此我们可是使用三维数组存储在任意状态下所能获得的最多石子数. S[i][m][0]表示当前石堆起始堆编号为i, M取值为m时,正在参与游戏的用户所能取得的最多石子个数. S[i][m][1]表示当前石堆时期编号为i, M取值为m,处于等待状态的用户所能取得的最多石子个数.

  • 确定状态转移方程
    S[i][m][0]=max{sum(piles[i],...,piles[i+x1])+S[i+x][max(x,m)][1],1xm}S[i][m][0] = max\{sum(piles[i],..., piles[i+x-1]) + S[i+x][max(x, m)][1], 1 \leq x \leq m\}
    若游戏用户当前最优选择为x,则S[i][m][1]=S[i+x][max(x,m)][0]S[i][m][1] = S[i+x][max(x, m)][0]

  • 确定基础状态
    如果只剩一堆石子,即i = piles.length-1, 则S[i][m][0] = piles[piles.length-1], S[i][m][1] = 0; 对于所有的m, 1mi1 \leq m \leq i.
    特别需要注意的是, 状态变量MM的取值范围为1Mi1 \leq M \leq i 这一条件并不适用与i=0时的情形,因为i=0时, M =1;
    S[0][1][0] = max{piles[0] + S[1][1][1], piles[0]+piles[1] + S[2][2][1]}

java算法:

class Solution {
    //定义前缀数组
    //状态变量, 当前剩余石子堆数起始编号, 当前M值, 当前执行用户

    //S[i][m][0] = max{cumSum(piles[i], ...piles[i+x-1]) + S[i+x][max(x, m)][1];1<=x<=2M}
    //S[i][m][1] = S[i+x][max(x, m)][0]

    public int stoneGameII(int[] piles) {
        int[] cumSum = new int[piles.length];
        cumSum[0] = piles[0];
        for(int i = 1; i < piles.length; i++)
            cumSum[i] = cumSum[i-1] + piles[i];
        
        int[][][] S = new int[piles.length+1][piles.length][2];
        
        //由于M = max(M, X), 因此如果当前起始堆下标为s,则M最大值为s, 表示第一用户一次性选择s堆
        //如果当前仅剩一堆,则无论当前M如何,第一个用户将拿走该堆,第二个用户为空
        for(int m = 1, s = piles.length-1; m <= s; m++){
            S[piles.length-1][m][0] = piles[piles.length-1];
            S[piles.length-1][m][1] = 0;
        }
        for(int s = piles.length-2; s > 0; s--){
            for(int m = 1; m <= s; m++){
                //当前用户可选择的x
                S[s][m][0] = 0;
                int maxX = 1;
                for(int x = 1; x <= 2*m && x <= piles.length-s; x++){
                    int stones = S[s+x][Math.max(x, m)][1] + cumSum[s+x-1] - cumSum[s-1];
                    if(S[s][m][0] < stones){
                        S[s][m][0] = stones;
                        maxX = x;
                    }
                }
                S[s][m][1] = S[s+maxX][Math.max(maxX, m)][0];
            }
        }
        //x = 1;
        if(piles.length > 1){
            S[0][1][0] = Math.max(piles[0] + S[1][1][1], piles[0]+piles[1] + S[2][2][1]);
            return S[0][1][0];
        }
        else{
            return piles[0];
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章