这是我参与更文挑战的第30天,活动详情查看: 更文挑战
石子游戏(题号877)
题目
亚历克斯和李用几堆石子在做游戏。偶数堆石子排成一行,每堆都有正整数颗石子 piles[i] 。
游戏以谁手中的石子最多来决出胜负。石子的总数是奇数,所以没有平局。
亚历克斯和李轮流进行,亚历克斯先开始。 每回合,玩家从行的开始或结束处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止,此时手中石子最多的玩家获胜。
假设亚历克斯和李都发挥出最佳水平,当亚历克斯赢得比赛时返回 true ,当李赢得比赛时返回 false 。
示例:
输入:[5,3,4,5]
输出:true
解释:
亚历克斯先开始,只能拿前 5 颗或后 5 颗石子 。
假设他取了前 5 颗,这一行就变成了 [3,4,5] 。
如果李拿走前 3 颗,那么剩下的是 [4,5],亚历克斯拿走后 5 颗赢得 10 分。
如果李拿走后 5 颗,那么剩下的是 [3,4],亚历克斯拿走后 4 颗赢得 9 分。
这表明,取前 5 颗石子对亚历克斯来说是一个胜利的举动,所以我们返回 true 。
提示:
2 <= piles.length <= 500piles.length是偶数。1 <= piles[i] <= 500sum(piles)是奇数。
链接
解释
这题啊,这题是令人发指。
此题涉及到博弈论,关于博弈论的解法放到后面再说,先看看常规解法。
常规解法就是经典DP了,可以搞一个二维数组,DP[i][j]用来表示,在i到j范围内可以得到的最大差值,也就是亚历克斯可以赢的最多点数。
这里涉及的问题就是如何选择到最大值,官方直接给了这样的解释👇:
当
i<j时,当前玩家可以选择取走piles[i]或piles[j],然后轮到另一个玩家在剩下的石子堆中取走石子。在两种方案中,当前玩家会选择最优的方案,使得自己的石子数量最大化。因此可以得到如下状态转移方程:
这波笔者就很迷惑了,怎么突然就来个这个?
为啥要用plies[i]-dp[i+1][j]和piles[j]−dp[i][j−1]?笔者一直思考了有十几分钟才反应过来,真是愚钝啊。
比方说当前是数组是这样的:
[1, 2, 3, 4]
此时的i是1,j是2。那么dp[i][j]应该是1,因为3-2=1嘛,亚历克斯应该回选择最优解法,所以必然会选择3,李就只能选择2了,所以亚历克斯赢的点数就是1。
如果此时i变成了i-1,那么此时选择的范围就变成了👇:
[1, 2, 3]
此时是奇数,他有两种选择方式:
-
选1
选1的时候,由于到亚历克斯选择了,那么在
[2, 3]这个范围中肯定是李先做选择,如果是李先做选择,那么在[2, 3]这个区间内必然是李拥有最大值,所以此时的dp[i + 1][j]对于亚历克斯来说就是一个负值。所以,需要用1减去
dp[i + 1][j]来得到此时亚历克斯可能获得的最大值。 -
选3
选3的时候和上面一样,曾经的最大值会到李手里,所以需要用3减去
dp[i][j - 1]来获取亚历克斯的最大值。
就这两块逻辑是真的绕,真的看了很久,加上歇歇画画才理解了。
自己的答案
无
更好的方法(动态规划)
在动态规划这里,需要处理一些特殊情况。
-
i === j这种情况意味着数组只有一个元素,而且是亚历克斯先选,所以
dp[i][i] = piles[i] -
i > j这种情况不存在,因为
i是起始值,j是结束值,所以i必须大于j。
处理完这些特殊情况之后,就可以进行计算了👇:
var stoneGame = function(piles) {
var len = piles.length
dp = Array.from({length: len}, () => new Array(len).fill(0))
for (let i = 0; i < len; i++) {
dp[i][i] = piles[i]
}
for (let i = len - 2; i > -1; i--) {
for (let j = i + 1; j < len; j++) {
dp[i][j] = Math.max(piles[i] - dp[i + 1][j], piles[j] - dp[i][j - 1])
}
}
return dp[0][len - 1] > 0
};
默认先填0,之后给dp[i][i]进行赋值。
完事之后开始两层循环,注意,这里是从大到小进行循环的 ,因为有可能选择右边的石子堆。
最后返回从0到数组长度的值就好了,如果这个值大于0,证明亚历克斯赢了,否则是李赢。
更好的方法(动态规划 -> 降维)
降维操作也是有的,跟其他的DP差不多👇:
var stoneGame = function(piles) {
var len = piles.length
dp = [...piles]
for (let i = len - 2; i > -1; i--) {
dp[i] = Math.max(piles[i] - dp[i + 1], piles[i] - dp[i])
}
return dp[len - 1] > 0
};
这块没啥可说的,经典降维。
更好的方法(数学)
这是笔者能看懂的为数不多的数学解法。
这里首先将数组分为两堆,一堆是奇数,一堆是偶数。
下面开始进行选择:
-
亚历克斯选择了数组第一个元素
此时亚历克斯选择的数奇数堆中的第一个元素,而李能选择的只能是数组的第二个元素或者是最后一个元素,这两个元素都是偶数
index,所以李必然会在偶数堆中进行选择。之后又到亚历克斯进行选择。
如果亚历克斯选择了奇数堆中的元素,李只能选择偶数堆中的元素,以此类推。
-
亚历克斯选择了数组最后一个元素
那么此时李能选择了只有奇数堆中的元素,不管李选择哪个元素,亚历克斯依然可以从偶数堆或者是奇数堆中的进行任意选择。
到这里答案就出来了,关键就是亚历克斯可以决定李选择的堆。
既然如果,亚历克斯可以通过自己的选择来左右李的选择,如果亚历克斯一直都选择奇数堆,李只能选择奇数堆。
同理,如果亚历克斯一直选择偶数堆,那么李就只能选择奇数堆。
根据题意,亚历克斯和李必然会有一方获胜,不可能存在平手的情况,那就证明了偶数堆和奇数堆中必然有一个是更大的,不存在两个堆一样大。
那么如果在一开始亚历克斯就找到了更大的堆,并且一直选择这个堆——亚历克斯必胜。
所以,数学方法的解法非常简单👇:
var stoneGame = function(piles) {
return true
};
是的,你没有看错,直接返回true就行,亚历克斯必胜。
这题好像有点涉及到博弈论,具体笔者这里就不多赘述了,因为笔者也不太清楚...
PS:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇
有兴趣的也可以看看我的个人主页👇