Skip to content

Commit

Permalink
feat: $1872
Browse files Browse the repository at this point in the history
  • Loading branch information
robot committed Jun 18, 2021
1 parent a30a8a0 commit 0bf29d3
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 3 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,8 @@ leetcode 题解,记录自己的 leetcode 解题之路。
- [1723. 完成所有工作的最短时间](./problems/1723.find-minimum-time-to-finish-all-jobs.md)
- [1787. 使所有区间的异或结果为零](./problems/1787.make-the-xor-of-all-segments-equal-to-zero.md)
- [1835. 所有数对按位与结果的异或和](./problems/1835.find-xor-sum-of-all-pairs-bitwise-and.md)
- [5775. 准时抵达会议现场的最小跳过休息次数](./problems/5775.minimum-skips-to-arrive-at-meeting-on-time.md)
- [1872. 石子游戏 VIII](./problems/1872.stone-game-viii.md)
- [1883. 准时抵达会议现场的最小跳过休息次数](./problems/5775.minimum-skips-to-arrive-at-meeting-on-time.md)

## :trident:  anki 卡片

Expand Down
3 changes: 2 additions & 1 deletion SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@
- [1723. 完成所有工作的最短时间](./problems/1723.find-minimum-time-to-finish-all-jobs.md) 🆕
- [1787. 使所有区间的异或结果为零](./problems/1787.make-the-xor-of-all-segments-equal-to-zero.md) 🆕
- [1835. 所有数对按位与结果的异或和](./problems/1835.find-xor-sum-of-all-pairs-bitwise-and.md) 🆕
- [5775. 准时抵达会议现场的最小跳过休息次数](./problems/5775.minimum-skips-to-arrive-at-meeting-on-time.md) 🆕
- [1872. 石子游戏 VIII](./problems/1872.stone-game-viii.md)
- [1883. 准时抵达会议现场的最小跳过休息次数](./problems/5775.minimum-skips-to-arrive-at-meeting-on-time.md) 🆕

- [后序](epilogue.md)
236 changes: 236 additions & 0 deletions problems/1872.stone-game-viii.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
## 题目地址(1872. 石子游戏 VIII)

https://leetcode-cn.com/problems/stone-game-viii/

## 题目描述

```
Alice 和 Bob 玩一个游戏,两人轮流操作, Alice 先手 。
总共有 n 个石子排成一行。轮到某个玩家的回合时,如果石子的数目 大于 1 ,他将执行以下操作:
选择一个整数 x > 1 ,并且 移除 最左边的 x 个石子。
将 移除 的石子价值之 和 累加到该玩家的分数中。
将一个 新的石子 放在最左边,且新石子的值为被移除石子值之和。
当只剩下 一个 石子时,游戏结束。
Alice 和 Bob 的 分数之差 为 (Alice 的分数 - Bob 的分数) 。 Alice 的目标是 最大化 分数差,Bob 的目标是 最小化 分数差。
给你一个长度为 n 的整数数组 stones ,其中 stones[i] 是 从左边起 第 i 个石子的价值。请你返回在双方都采用 最优 策略的情况下,Alice 和 Bob 的 分数之差 。
 
示例 1:
输入:stones = [-1,2,-3,4,-5]
输出:5
解释:
- Alice 移除最左边的 4 个石子,得分增加 (-1) + 2 + (-3) + 4 = 2 ,并且将一个价值为 2 的石子放在最左边。stones = [2,-5] 。
- Bob 移除最左边的 2 个石子,得分增加 2 + (-5) = -3 ,并且将一个价值为 -3 的石子放在最左边。stones = [-3] 。
两者分数之差为 2 - (-3) = 5 。
示例 2:
输入:stones = [7,-6,5,10,5,-2,-6]
输出:13
解释:
- Alice 移除所有石子,得分增加 7 + (-6) + 5 + 10 + 5 + (-2) + (-6) = 13 ,并且将一个价值为 13 的石子放在最左边。stones = [13] 。
两者分数之差为 13 - 0 = 13 。
示例 3:
输入:stones = [-10,-12]
输出:-22
解释:
- Alice 只有一种操作,就是移除所有石子。得分增加 (-10) + (-12) = -22 ,并且将一个价值为 -22 的石子放在最左边。stones = [-22] 。
两者分数之差为 (-22) - 0 = -22 。
 
提示:
n == stones.length
2 <= n <= 10^5
-10^4 <= stones[i] <= 10^4
```

## 前置知识

- 动态规划
- 前缀和

## 公司

- 暂无

## 前缀和 + 暴力枚举记忆化递归(n^2 时间复杂度)

### 思路

看下数据范围是 $10^5$, 这意味着 $n^2$ 不可行,不过我仍然想先从暴力解法开始,帮助大家理清思路。

首先我们需要观察到:游戏从始到终的**石子值总和是不变的**,因此每次玩家选择拿前 x 的时候, 得到的分数一定是原始石子数组的**前缀和**。因此不难想到使用前缀和加速分数的计算。

接下来,我们尝试模拟游戏的进行。

- 刚开始,alice 可以选择前 x 项,其中 1 < x <= n,得到 pres[x] 的分数,其中 pres 为原数组的前缀和。
- 接下来,bob 也可以选择前 x 项,其中 x < x' <= n - x,得到 pres[x'] 的分数。
- 。。。

我们可以使用递归来模拟 alice 和 bob 交替决策的过程,用 for 循环模拟取前 x 项的过程,并使用记忆化来保存中间过程以避免重复计算。

### 关键点

- 前缀和
- 动态规划

### 代码

- 语言支持:Python3

Python3 Code:

```python

from itertools import accumulate

class Solution:
def stoneGameVIII(self, stones: List[int]) -> int:
pres = list(accumulate(stones))

@cache
def dp(pos):
if pos == len(stones):
return 0
ans = float("-inf")
for nxt in range(pos, len(stones)):
ans = max(ans, pres[nxt] - dp(nxt + 1))
return ans

return dp(1)

```

**复杂度分析**

令 n 为数组长度。

- 时间复杂度:$O(n^2)$
- 空间复杂度:$O(n)$

## 优化记忆化递归

### 思路

上面是简单的模拟。

实际上,我们可以进行一个优化。使用 dp(i) 表示还剩下 [i, n) 要选择的情况下,Alice 所能得到的最大分数差。

对于某个玩家来说,其对应决策可以分为两种:

- 选取当前数及之前的所有数(等价于 pres[pos],其中 pos 为上个玩家选完后的下个位置),那么 dp[i] = pres[i] - dp[i+1]

> 这是因为 bob 也会最大化发挥。
- 不选择当前数(可能选下一个,下下一个。。。 etc),那么 dp[i] = dp[i + 1]

### 代码

- 语言支持:Python3

Python3 Code:

```py
from itertools import accumulate


class Solution:
def stoneGameVIII(self, stones: List[int]) -> int:
pres = list(accumulate(stones))
n = len(stones)

@cache
def dp(pos):
if pos == n - 1:
return pres[n - 1]
return max(dp(pos + 1), pres[pos] - dp(pos + 1))

return dp(1)
```

**复杂度分析**

令 n 为数组长度。

- 时间复杂度:$O(n)$
- 空间复杂度:$O(n)$

## DP + 滚动数组优化

### 思路

将代码改造为 dp 也不难。

当你改造为了 dp,顺便也可以进行一个小小的优化,那就是使用滚动数组,节省了 dp 数组的开销,转而使用一个变量即可。

### 代码

- 语言支持:Python3

Python3 Code:

```py

class Solution:
def stoneGameVIII(self, stones: List[int]) -> int:
pres = list(accumulate(stones))
n = len(stones)
dp = [0] * n
dp[n - 1] = pres[n - 1]
for i in range(n - 2, 0, -1):
dp[i] = max(dp[i + 1], pres[i] - dp[i + 1])
return dp[1]

```

**复杂度分析**

令 n 为数组长度。

- 时间复杂度:$O(n)$
- 空间复杂度:$O(n)$

滚动数组优化:

```py

class Solution:
def stoneGameVIII(self, stones: List[int]) -> int:
pres = list(accumulate(stones))
n = len(stones)
ans = pres[n - 1]
for i in range(n - 2, 0, -1):
ans = max(ans, pres[i] - ans)
return ans
```

**复杂度分析**

令 n 为数组长度。

- 时间复杂度:$O(n)$
- 空间复杂度:$O(n)$。虽然没有使用 dp 数组,但是 pres 数组的空间仍然存在。

> 此题解由 [力扣刷题插件](https://leetcode-pp.github.io/leetcode-cheat/?tab=solution-template) 自动生成。
力扣的小伙伴可以[关注我](https://leetcode-cn.com/u/fe-lucifer/),这样就会第一时间收到我的动态啦~

以上就是本文的全部内容了。大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 40K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。

关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。

![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfcuzagjalj30p00dwabs.jpg)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## 题目地址(5775. 准时抵达会议现场的最小跳过休息次数)
## 题目地址(1883. 准时抵达会议现场的最小跳过休息次数)

https://leetcode-cn.com/problems/minimum-skips-to-arrive-at-meeting-on-time/

Expand Down

0 comments on commit 0bf29d3

Please sign in to comment.