leetcode 1140. Stone Game II(石头游戏II)

leetcode 1140. Stone Game II(石头游戏II)_第1张图片

涉及game的问题,2个player,
现有几堆石头,每堆石头个数为piles[i],
刚开始M=1,
player1先拿石头,可以拿走前 x 堆(假设从第 i 堆开始拿,可以拿 i ~ i+x-1 堆),1 <= x <= 2M,
拿完之后,重置M=max(x, M),
然后player2开始拿,重复这个过程,直到所有的石头被拿完。
问player1最多可以拿多少个石头。

思路:

game理论就是min, max, 尽量做到自己max, 对手min。
枚举所有可能的情况,找到player1最多的石头。

假设从第 i 堆开始拿,
player1拿x堆,那么player1将会得到piles[i] + … + piles[i+x-1]个石头,
M更新为max(x, M),
那么下一次player2从 i+x 堆开始拿,最多拿 2*max(x,M) 堆。
直到石头被拿完。

要枚举所有的 1 <= x <= 2M,
同时M <= n (n为石头堆数),2M >= n-x时,可以一次性拿走剩下所有石头(x=2M时,可以拿完)。

方法1:DFS

用DFS模拟一次player1, player2交替拿石头的过程,
因为中间涉及到计算和的过程:piles[i] + … + piles[i+x-1],
提前计算积分数组pileSum,那么piles[i] + … + piles[i+x-1] = pileSum[i] - pileSum[i+x] (从右到左求和)

dfs(pileSum, i, m, dp)表示player从第 i 堆开始拿x堆石头(遍历所有x),m为当前的M,
保证调用它的player能石头最大化。
dp[i][m]保存从第i堆开始拿,限制为m的最大可拿石头数,可以避免重复计算。

因为最后求的是player1最多拿多少个石头,所以用player1的视角来调用DFS.
player1先拿,从第0堆开始拿,M=1。
所以调用dfs(pileSum, 0, 1, dp)

现在假设到了第 i 堆,
player1从第i堆开始拿走x堆石头后,会得到piles[i] + … + piles[i+x-1]个石头,
pileSum计算的是从右到左的和,因此piles[i] + … + piles[i+x-1] = pileSum[i]-pileSum[i+x]
M更新为max(x, M)

player1拿完之后,player2从i+x堆开始拿,限制为新的M=max(x, M),
根据game理论,对于player2来说,它自己也要石头最大化,
所以从player2的视角调用DFS, dfs(pileSum, i+x, max(x,M), dp)

player2拿完之后,player1可以拿pileSum[i+x] - player2拿的部分,
这就得到了player1最后拿的石头数。

用player1的视角调用DFS时,DFS函数的内部也有player2视角调用的DFS,
反之player2的视角调用DFS时,内部也有player1调用的DFS,
这就解释了game理论中的min, max, 最大化自己,最小化对手。

class Solution {
    int n = 0;
    public int stoneGameII(int[] piles) {
        n = piles.length;
        int[][] dp = new int[n][n];
        
        for (int i = n - 2; i >= 0; i--)
            piles[i] += piles[i + 1];
        return dfs(piles, 0, 1, dp);
    }

    private int dfs(int[] pileSum, int i, int m, int[][] dp) {
        //x<=2m,如果i+2m>=n,说明可以一次取走剩下所有堆
        if (i + 2 * m >= n)
            return pileSum[i];
        
        if (dp[i][m] > 0)
            return dp[i][m];

        int result = 0, take = 0;
        for (int x = 1; x <= 2 * m; x++) {
            take = pileSum[i] - pileSum[i + x];  //拿走前x堆
            result = Math.max(result, take + pileSum[i + x] - dfs(pileSum, i+x, Math.max(x, m), dp));
        }
        dp[i][m] = result;
        return result;
    }
}

方法二:DP

上面的DFS可以看作top-down的过程,
那么DP就是一个bottom-up的过程。
效率的话DFS更快

    public int stoneGameII(int[] piles) {
        final int n = piles.length;
        if(n < 2) return piles[0];
        int[][] dp = new int[n][n+1];
        int sum = 0;
        
        //dp[i][m]: i表示从第i堆开始拿,m表示当前M
        for(int i = n-1; i >= 0; i--) {
            sum += piles[i];
            for(int m = 1; m <= n; m++) {
                if(i + 2*m >=n) dp[i][m] = sum;
                else {
                    for(int x = 1; x <= 2* m; x++)
                        dp[i][m] = Math.max(dp[i][m], sum - dp[i+x][Math.max(x,m)]);
                }
            }
      
        }
        return dp[0][1];
    }

你可能感兴趣的:(leetcode,leetcode,深度优先,dp,game,theory)