LeetCodeAnimation/notes/LeetCode第877号问题:石子游戏.md
程序员吴师兄 5dee53d957 更换图片地址
2019-11-14 11:00:28 +08:00

135 lines
5.5 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# LeetCode 877 号问题石子游戏
> 本文首发于公众号五分钟学算法[图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>)系列文章之一。
>
> 个人网站[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com)
### 题目描述
喜羊羊和灰太狼用几堆石子在做游戏偶数堆石子**排成一行**每堆都有正整数颗石子 `piles[i]`
游戏以谁手中的石子最多来决出胜负石子的总数是奇数所以没有平局
喜羊羊和灰太狼轮流进行喜羊羊先开始 每回合玩家从行的开始或结束处取走整堆石头 这种情况一直持续到没有更多的石子堆为止此时手中石子最多的玩家获胜
假设喜羊羊和灰太狼都发挥出最佳水平当喜羊羊赢得比赛时返回 `true` 当灰太狼赢得比赛时返回 `false`
### 题目分析
举两个例子来帮助理解题意
#### 例子一
输入[ 5345 ]
输出true
**解释**
喜羊羊先开始只能拿前 5 颗或后 5 颗石子
假设他取了前 5 这一行就变成了 [ 3 45 ]
如果灰太狼拿走前 3 那么剩下的是 [ 45 ]喜羊羊拿走后 5 颗赢得 10
如果灰太狼拿走后 5 那么剩下的是 [ 34 ]喜羊羊拿走后 4 颗赢得 9
这表明取前 5 颗石子对喜羊羊来说是一个胜利的举动所以我们返回 true
#### 例子二
输入[ 51000023 ]
输出true
**解释**
喜羊羊先开始只能拿前 5 颗或后 3 颗石子
假设他取了后 3 这一行就变成了 [ 5100002 ]
灰太狼肯定会在剩下的这一行中取走前 5 这一行就变成了 [ 100002 ]
然后喜羊羊取走前 10000 总共赢得 10003 灰太狼赢得 7
这表明取后 3 颗石子对喜羊羊来说是一个胜利的举动所以我们返回 true
**这个例子表明并不是需要每次都挑选最大的那堆石头**
### 题目回答
涉及到最优解的问题那么肯定要去尝试一下使用 **动态规划 **来解决了
先看一下力扣的正规题解
让我们改变游戏规则使得每当灰太狼得分时都会从喜羊羊的分数中扣除
`dp(i, j)` 为喜羊羊可以获得的最大分数其中剩下的堆中的石子数是 `piles[i], piles[i+1], ..., piles[j]`这在比分游戏中很自然我们想知道游戏中每个位置的值
我们可以根据 `dp(i + 1j)` `dp(ij-1)` 来制定 `dp(ij)` 的递归我们可以使用动态编程以不重复这个递归中的工作该方法可以输出正确的答案因为状态形成一个DAG有向无环图
当剩下的堆的石子数是 `piles[i], piles[i+1], ..., piles[j]` 轮到的玩家最多有 2 种行为
可以通过比较 `j-i` `N modulo 2` 来找出轮到的人
如果玩家是喜羊羊那么它将取走 `piles[i]` `piles[j]` 颗石子增加它的分数之后总分为 `piles[i] + dp(i+1, j)` `piles[j] + dp(i, j-1)`我们想要其中的最大可能得分
如果玩家是灰太狼那么它将取走 `piles[i]` `piles[j]` 颗石子减少喜羊羊这一数量的分数之后总分为 `-piles[i] + dp(i+1, j)` `-piles[j] + dp(i, j-1)`我们想要其中的最小可能得分
代码如下
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/af7fm.jpg)
上面的代码并不算复杂当然如果你看不懂也没关系不影响解决问题请看下面的数学分析
### 数学分析
因为石头的数量是奇数因此只有两种结果输或者赢
喜羊羊先开始拿石头随便拿然后比较石头数量
1. 如果石头数量多于对手赢了
2. 如果石头数量少于对手自己拿石头的顺序和对手拿石头的顺序对调**因为是偶数堆石头所以可以全部对调**还是赢
所以代码如下
```java
class Solution {
public boolean stoneGame(int[] piles) {
return true;
}
}
```
下面给给大家介绍一种简单的策略作为参考使用这种策略可以保证先取石头的喜羊羊一定能够获胜
首先分别计算出序号为奇数和序号为偶数的石头堆中的石头总数然后进行比较如果奇数堆石头总数更多则喜羊羊永远保证自己选取奇数石堆反之则选择偶数
举例来说假设石堆为 [ 51000023 ] 那么奇数石堆总和为 7 1 开始编号偶数石堆总数为 1003 则喜羊羊要保证自己永远选择偶数堆即第四堆和第二堆就可以取胜
但是这种选择方法得到的**结果未必是最优解**例如石堆为 [ 2135 ] 当使用动态规划确保喜羊羊和灰太狼都选择最优解的时候喜羊羊会拿走 [ 25 ] 两堆棋子而灰太狼则拿走 [ 13 ] 两堆但是使用这种策略在即使不是最优解的情况下依然可以保证喜羊羊胜利所以作为先手的喜羊羊必定有方法取得比赛的胜利
看完之后你的心情是怎么样的
此题的LeetCode 的评论区里一片吐槽**这是什么沙雕题目**
可能搞过 ACM 等竞赛的人都会微微一笑不会几万个套路怎么好意思说自己是 acmer 我们这些普通人为之惊奇的题目到他们这里就是彻底被玩坏了各种稀奇古怪的秒解
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/lnwx8.png)