[leetcode]1140.捡石头(动态规划做法)




题目

img

动态规划

动态规划三步走

  • 定义dp数组意义
  • 找出状态转移方程
  • 明确基本情况

定义dp数组意义

dp[i][j]表示M = i的情况下,剩余piles[j : len - 1]堆时,先取的人能获得的最多石子数
行为i 列为j  因为行多了一行j为0的情况  这里先不考虑优化数组空间,重在思路

找出状态转移方程

一、 len - i <= 2M
	剩下的堆数能够直接全部取走,那么最优的情况就是剩下的石子总和
	dp[i][M] = sum[i : len - 1]

二、 i + 2M < len
	其中 1 <= x <= 2M,剩下的堆数不能全部取走,那么最优情况就是让下一个人取的更少。
	对于所有的x取值,下一个人从x开始取起,M变为max(M, x)
	下一个人能取dp[i + x][max(M, x)]
	我最多能取sum[i : len - 1] - dp[i + x][max(M, x)]。
	dp[i][M] = max(dp[i][M], sum[i : len - 1] - dp[i + x][max(M, x)])

base case

刚开始想的时候,就理所当然地认为最后一列全都等于piles[len-1],因为这种情况下,无论怎么拿,都是得拿完的。

// 基本情况 最后一列 此时i=len-1,即只剩下一堆
for (int row = 1; row < len + 1; row++) {
	dp[row][len - 1] = piles[len - 1];
}

不过其实,只要是剩下的堆数能够直接全部取走,这种都属于基本情况。

那么最优的情况就是剩下的石子总和

dp[i][M] = sum[i : len - 1]

代码

class Solution {
   public int stoneGameII(int[] piles) {
        int len = piles.length;
        if(len < 1){
            return 0;
        }
        if(len == 1){
            return piles[0];
        }

        int sum = 0;

        // dp[i][j]表示M = i的情况下,剩余piles[j : len - 1]堆时,先取的人能获得的最多石子数
        // 行为i 列为j  因为行多了一行j为0的情况  这里先不考虑优化数组空间,重在思路

        int[][] dp = new int[len+1][len];

        // 基本情况 最后一列 此时i=len-1,即只剩下一堆
    //    for (int row = 1; row < len + 1; row++) {
    //        dp[row][len - 1] = piles[len - 1];
    //    }


        // 从后往前推,推到第一列
        for (int j = len - 1; j >= 0; j--) {
            sum += piles[j];

            for (int M = 1; M <= len; M++) {
                // 可以一次性全部拿完  剩余堆数小于2*M时
                if (len - j <= 2 * M) {
                    dp[M][j] = sum;
                } else {
                    for (int x = 1; x <= 2 * M; x++) {
                        dp[M][j] = Math.max(
                                dp[M][j],
                                sum - dp[Math.max(M, x)][j + x]
                        );
                    }
                }
            }
        }

        // 答案为dp[0][1]
        // 剩余piles[0 : len - 1]堆时,M = 1的情况下,先取的人能获得的最多石子数
        return dp[1][0];
    }

}

img




其它做法

一般是回溯法+备忘录

这种用python写思路比较清晰。

class Solution:
    def stoneGameII(self, piles: List[int]) -> int:
        dp = {}
        N = len(piles)

        # 输入当前的M以及当前剩余石子堆的位置,返回先手能获得的最大积分及该情况下后手积分
        def max_get(M, start):
            if (M, start) in dp:
                return dp[M, start]
            
            count = N-start  # 剩余堆数

            if count <= 2*M:  # 全部拿完
                dp[M, start] = sum(piles[start:]), 0
            else:

                # 记录先手能获得的最大积分及该情况下后手积分,get为本次拿的积分
                max_pre, max_post, get = 0, 0, 0
                for x in range(min(2*M, count)):

                     # 实际拿的堆数为x+1
                    _M = max(M, x+1) 

                    get += piles[start+x]

                    pre, post = max_get(_M, start+x+1)  # 回溯

                    # get+post为当前拿到的积分+回溯下的后手积分
                    if get+post > max_pre:  
                        # 替换最大值
                        max_pre, max_post = get+post, pre  
                        
                dp[M, start] = max_pre, max_post
            return dp[M, start]
	return max_get(1, 0)[0]
class Solution:
    def stoneGameII(self, piles: List[int]) -> int:
        # 数据规模与记忆化
        n, memo = len(piles), dict()
        
        # s[i] 表示第 i 堆石子到最后一堆石子的总石子数
        s = [0] * (n + 1)
        for i in range(n - 1, -1, -1):
            s[i] = s[i + 1] + piles[i]
            
        # dfs(i, M) 表示从第 i 堆石子开始取,最多能取 M 堆石子所能得到的最优值
        def dfs(i, M):
            # 如果已经搜索过,直接返回
            if (i, M) in memo:
                return memo[(i, M)]
            # 溢出拿不到任何石子
            if i >= n:
                return 0
            # 如果剩余堆数小于等于 2M, 那么可以全拿走
            if i + M * 2 >= n:
                return s[i]
            # 枚举拿 x 堆的最优值
            best = 0
            for x in range(1, M * 2 + 1):
                # 剩余石子减去对方最优策略
                best = max(best, s[i] - dfs(i + x, max(x, M)))
            # 记忆化
            memo[(i, M)] = best
            return best
        
        return dfs(0, 1)




结语

博弈问题的前提一般都是在两个聪明人之间进行,编程描述这种游戏的一般方法是二维 dp 数组,数组中通过元组分别表示两人的最优决策。

之所以这样设计,是因为先手在做出选择之后,就成了后手,后手在对方做完选择后,就变成了先手。这种角色转换使得我们可以重用之前的结果,典型的动态规划标志。

参考资料

《动态规划之博弈问题》 labuladong

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值