这是我参与更文挑战的第2天,活动详情查看: 更文挑战
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 <= 500
piles.length 是偶数。
1 <= piles[i] <= 500
sum(piles) 是奇数。
解题思路分析
方式一:数学
遇上这种类型的题目,总会觉得该数组是有规律的,大概率是先手获胜。(肯定是力扣养成的习惯~)
根据提示我们可知数组长度为偶数,那么我们就可以将数组按照奇数偶数来进行区分,分别统计每组元素的总和,先手的玩家只要选择总和大的一组数据即可获得最终的胜利。
例如:1410851,按照分组可得 A组:1 10 5,B组:4 8 1。
因为亚历克斯是先手,那么他就可以选择总和大的这一组先取走一堆石子1。
此时剩下的石子数组为 410851,不管是首位元素还是末位元素,都是B组中的元素,不管李拿那一边的石子,我们都可以拿到A组中的石子。
因此,在该题为先手的情况下,亚历克斯必胜。
代码
class Solution {
public boolean stoneGame(int[] piles) {
return true;
}
}
执行结果
复杂度分析
时间复杂度:O(1)。
空间复杂度:O(1)。
方式二:动态规划
因为每次只能从首尾两处任选一处来取走石子,所以我们可以知晓剩余的石子一定是连续的。
那么我们就可以定义一个二维数组来维护一个区间[left, right],由于right小于left没有意义,那么我们对数组进行初始化的时候只需要先将每一位元素的区间进行一个更新即可。(即left == right)
当left < right 的时候,当前玩家可以选择取走首位或者末位石子,然后轮得另一位玩家继续游戏,假设两位玩家都发挥出最佳水平,适得自己的石子数量最大化,那么我们可以得出区间
dp[left][right] = max(piles[i] - dp[i+ 1][j], piles[j] - dp[i][j - 1])
公式已经得到了,那么就可以开始实现了,因为dp数组中只初始化了每一位元素各自的区间,那么我们需要从末行往前进行一个倒推
代码
class Solution {
public boolean stoneGame(int[] piles) {
int n = piles.length;
// 定义dp数组
int[][] dp = new int[n][n];
// 初始化区间
for(int i = 0; i < n; ++i){
dp[i][i] = piles[i];
}
// 末行开始往前倒推
for(int i = n - 2; i >= 0; --i){
// 得到区间[i, j]亚历克斯比对方多的石子数量
for(int j = i + 1; j < n; ++j){
dp[i][j] = Math.max(piles[i] - dp[i + 1][j], piles[j] - dp[i][j - 1]);
}
}
// 返回结果,当该区间大于0时,表示亚历克斯获得的石子数量比对方多
return dp[0][n - 1] > 0;
}
}
执行结果
复杂度分析
时间复杂度:O(n^2)。
空间复杂度:O(n^2)。
题目来源力扣:leetcode-cn.com/problems/st…