动态规划之博弈问题

你和你的朋友⾯前有⼀排⽯头堆, ⽤⼀个数组 piles 表⽰, piles[i] 表⽰第 i堆⽯⼦有多少个。 你们轮流拿⽯头, ⼀次拿⼀堆, 但是只能拿⾛最左边或者最右边的⽯头堆。 所有⽯头被拿完后, 谁拥有的⽯头多, 谁获胜。


⽯头的堆数可以是任意正整数, ⽯头的总数也可以是任意正整数, 这样就能打破先⼿必胜的局⾯了。 ⽐如有三堆⽯头 piles = [1, 100, 3] , 先⼿不管拿 1 还是 3, 能够决定胜负的 100 都会被后⼿拿⾛, 后⼿会获胜。
dp[i][j][fir]表示对于piles[i,....j]这部分石头,先手所能获得的最大分数

dp[i][j][sec]表示对于piles[i,....j]这部分石头,后手所能获得的最大分数

举例理解⼀下, 假设 piles = [3, 9, 1, 2], 索引从 0 开始
dp[0][1].fir = 9 意味着:⾯对⽯头堆 [3, 9], 先⼿最终能够获得 9 分。
dp[1][3].sec = 2 意味着:⾯对⽯头堆 [9, 1, 2], 后⼿最终能够获得 2 分
 

dp状态数组的含义一定要表示清楚,同时要注意先后手身份的转变,当先手取完石子后,先手的状态就应该变为后手

dp[i][j].fir = max(piles[i] + dp[i+1][j].sec, piles[j] + dp[i][j-1].sec)
dp[i][j].fir = max( 选择最左边的⽯头堆 , 选择最右边的⽯头堆 )

# 解释:我作为先⼿, ⾯对 piles[i...j] 时, 有两种选择:
# 要么我选择最左边的那⼀堆⽯头, 然后⾯对 piles[i+1...j]
# 但是此时轮到对⽅, 相当于我变成了后⼿;
# 要么我选择最右边的那⼀堆⽯头, 然后⾯对 piles[i...j-1]
# 但是此时轮到对⽅, 相当于我变成了后⼿。


if 先⼿选择左边:
dp[i][j].sec = dp[i+1][j].fir
if 先⼿选择右边:
dp[i][j].sec = dp[i][j-1].fir

# 解释:我作为后⼿, 要等先⼿先选择, 有两种情况:
# 如果先⼿选择了最左边那堆, 给我剩下了 piles[i+1...j]
# 此时轮到我, 我变成了先⼿;
# 如果先⼿选择了最右边那堆, 给我剩下了 piles[i...j-1]
# 此时轮到我, 我变成了先⼿。

然后我们会发现只需要dp数组的右上办部分,对于dp[i][j]的计算依赖于dp[i-1][j]和dp[i][j-1].因此应该斜的遍历数组,同时对于初始情况初始化,应该只初始化对角线上的内容。

代码如下:

int stoneGame(int[] piles) {
int n = piles.length;
// 初始化 dp 数组
Pair[][] dp = new Pair[n][n];
for (int i = 0; i < n; i++)
    for (int j = i; j < n; j++)
        dp[i][j] = new Pair(0, 0);
        // 填⼊ base case
for (int i = 0; i < n; i++) {
    dp[i][i].fir = piles[i];
    dp[i][i].sec = 0;
} /
/ 斜着遍历数组
for (int l = 2; l <= n; l++) {
    for (int i = 0; i <= n - l; i++) {
    int j = l + i - 1;
// 先⼿选择最左边或最右边的分数
    int left = piles[i] + dp[i+1][j].sec;
    int right = piles[j] + dp[i][j-1].sec;
// 套⽤状态转移⽅程
    if (left > right) {
        dp[i][j].fir = left;
        dp[i][j].sec = dp[i+1][j].fir;
}     else {
        dp[i][j].fir = right;
        dp[i][j].sec = dp[i][j-1].fir;
 }
}
}   
Pair res = dp[0][n-1];
return res.fir - res.sec;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值