From e3fd0d26710652d108be3a62c7fcd311e313bd39 Mon Sep 17 00:00:00 2001 From: YDZ Date: Thu, 17 Dec 2020 12:57:54 +0800 Subject: [PATCH] =?UTF-8?q?Add=20solution=201674=E3=80=811690?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...nimum Moves to Make Array Complementary.go | 34 +++++ ... Moves to Make Array Complementary_test.go | 53 +++++++ .../README.md | 97 +++++++++++++ .../1690. Stone Game VII.go | 65 +++++++++ .../1690. Stone Game VII_test.go | 47 +++++++ leetcode/1690.Stone-Game-VII/README.md | 130 ++++++++++++++++++ ...nimum-Moves-to-Make-Array-Complementary.md | 97 +++++++++++++ .../ChapterFour/1690.Stone-Game-VII.md | 130 ++++++++++++++++++ website/content/menu/index.md | 2 + 9 files changed, 655 insertions(+) create mode 100644 leetcode/1674.Minimum-Moves-to-Make-Array-Complementary/1674. Minimum Moves to Make Array Complementary.go create mode 100644 leetcode/1674.Minimum-Moves-to-Make-Array-Complementary/1674. Minimum Moves to Make Array Complementary_test.go create mode 100644 leetcode/1674.Minimum-Moves-to-Make-Array-Complementary/README.md create mode 100644 leetcode/1690.Stone-Game-VII/1690. Stone Game VII.go create mode 100644 leetcode/1690.Stone-Game-VII/1690. Stone Game VII_test.go create mode 100644 leetcode/1690.Stone-Game-VII/README.md create mode 100644 website/content/ChapterFour/1674.Minimum-Moves-to-Make-Array-Complementary.md create mode 100644 website/content/ChapterFour/1690.Stone-Game-VII.md diff --git a/leetcode/1674.Minimum-Moves-to-Make-Array-Complementary/1674. Minimum Moves to Make Array Complementary.go b/leetcode/1674.Minimum-Moves-to-Make-Array-Complementary/1674. Minimum Moves to Make Array Complementary.go new file mode 100644 index 000000000..cd8ef3b42 --- /dev/null +++ b/leetcode/1674.Minimum-Moves-to-Make-Array-Complementary/1674. Minimum Moves to Make Array Complementary.go @@ -0,0 +1,34 @@ +package leetcode + +func minMoves(nums []int, limit int) int { + diff := make([]int, limit*2+2) // nums[i] <= limit, b+limit+1 is maximum limit+limit+1 + for j := 0; j < len(nums)/2; j++ { + a, b := min(nums[j], nums[len(nums)-j-1]), max(nums[j], nums[len(nums)-j-1]) + // using prefix sum: most interesting point, and is the key to reduce complexity + diff[2] += 2 + diff[a+1]-- + diff[a+b]-- + diff[a+b+1]++ + diff[b+limit+1]++ + } + cur, res := 0, len(nums) + for i := 2; i <= 2*limit; i++ { + cur += diff[i] + res = min(res, cur) + } + return res +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} diff --git a/leetcode/1674.Minimum-Moves-to-Make-Array-Complementary/1674. Minimum Moves to Make Array Complementary_test.go b/leetcode/1674.Minimum-Moves-to-Make-Array-Complementary/1674. Minimum Moves to Make Array Complementary_test.go new file mode 100644 index 000000000..c111bf145 --- /dev/null +++ b/leetcode/1674.Minimum-Moves-to-Make-Array-Complementary/1674. Minimum Moves to Make Array Complementary_test.go @@ -0,0 +1,53 @@ +package leetcode + +import ( + "fmt" + "testing" +) + +type question1674 struct { + para1674 + ans1674 +} + +// para 是参数 +// one 代表第一个参数 +type para1674 struct { + nums []int + limit int +} + +// ans 是答案 +// one 代表第一个答案 +type ans1674 struct { + one int +} + +func Test_Problem1674(t *testing.T) { + + qs := []question1674{ + + { + para1674{[]int{1, 2, 4, 3}, 4}, + ans1674{1}, + }, + + { + para1674{[]int{1, 2, 2, 1}, 2}, + ans1674{2}, + }, + + { + para1674{[]int{1, 2, 1, 2}, 2}, + ans1674{0}, + }, + } + + fmt.Printf("------------------------Leetcode Problem 1674------------------------\n") + + for _, q := range qs { + _, p := q.ans1674, q.para1674 + fmt.Printf("【input】:%v 【output】:%v\n", p, minMoves(p.nums, p.limit)) + } + fmt.Printf("\n\n\n") +} diff --git a/leetcode/1674.Minimum-Moves-to-Make-Array-Complementary/README.md b/leetcode/1674.Minimum-Moves-to-Make-Array-Complementary/README.md new file mode 100644 index 000000000..8e565e10a --- /dev/null +++ b/leetcode/1674.Minimum-Moves-to-Make-Array-Complementary/README.md @@ -0,0 +1,97 @@ +# [1674. Minimum Moves to Make Array Complementary](https://leetcode.com/problems/minimum-moves-to-make-array-complementary/) + +## 题目 + +You are given an integer array `nums` of **even** length `n` and an integer `limit`. In one move, you can replace any integer from `nums` with another integer between `1` and `limit`, inclusive. + +The array `nums` is **complementary** if for all indices `i` (**0-indexed**), `nums[i] + nums[n - 1 - i]` equals the same number. For example, the array `[1,2,3,4]` is complementary because for all indices `i`, `nums[i] + nums[n - 1 - i] = 5`. + +Return the ***minimum** number of moves required to make* `nums` ***complementary***. + +**Example 1:** + +``` +Input: nums = [1,2,4,3], limit = 4 +Output: 1 +Explanation: In 1 move, you can change nums to [1,2,2,3] (underlined elements are changed). +nums[0] + nums[3] = 1 + 3 = 4. +nums[1] + nums[2] = 2 + 2 = 4. +nums[2] + nums[1] = 2 + 2 = 4. +nums[3] + nums[0] = 3 + 1 = 4. +Therefore, nums[i] + nums[n-1-i] = 4 for every i, so nums is complementary. +``` + +**Example 2:** + +``` +Input: nums = [1,2,2,1], limit = 2 +Output: 2 +Explanation: In 2 moves, you can change nums to [2,2,2,2]. You cannot change any number to 3 since 3 > limit. +``` + +**Example 3:** + +``` +Input: nums = [1,2,1,2], limit = 2 +Output: 0 +Explanation: nums is already complementary. +``` + +**Constraints:** + +- `n == nums.length` +- `2 <= n <= 105` +- `1 <= nums[i] <= limit <= 105` +- `n` is even. + +## 题目大意 + +给你一个长度为 偶数 n 的整数数组 nums 和一个整数 limit 。每一次操作,你可以将 nums 中的任何整数替换为 1 到 limit 之间的另一个整数。 + +如果对于所有下标 i(下标从 0 开始),nums[i] + nums[n - 1 - i] 都等于同一个数,则数组 nums 是 互补的 。例如,数组 [1,2,3,4] 是互补的,因为对于所有下标 i ,nums[i] + nums[n - 1 - i] = 5 。 + +返回使数组 互补 的 最少 操作次数。 + +## 解题思路 + +- 这一题考察的是差分数组。通过分析题意,可以得出,针对每一个 `sum` 的取值范围是 `[2, 2* limt]`,定义 `a = min(nums[i], nums[n - i - 1])`,`b = max(nums[i], nums[n - i - 1])`,在这个区间内,又可以细分成 5 个区间,`[2, a + 1)`,`[a + 1, a + b)`,`[a + b + 1, a + b + 1)`,`[a + b + 1, b + limit + 1)`,`[b + limit + 1, 2 * limit)`,在这 5 个区间内使得数组互补的最小操作次数分别是 `2(减少 a, 减少 b)`,`1(减少 b)`,`0(不用操作)`,`1(增大 a)`,`+2(增大 a, 增大 b)`,换个表达方式,按照扫描线从左往右扫描,在这 5 个区间内使得数组互补的最小操作次数叠加变化分别是 `+2(减少 a, 减少 b)`,`-1(减少 a)`,`-1(不用操作)`,`+1(增大 a)`,`+1(增大 a, 增大 b)`,利用这前后两个区间的关系,就可以构造一个差分数组。差分数组反应的是前后两者的关系。如果想求得 0 ~ n 的总关系,只需要求一次前缀和即可。 +- 这道题要求输出最少的操作次数,所以利用差分数组 + 前缀和,累加前缀和的同时维护最小值。从左往右扫描完一遍以后,输出最小值即可。 + +## 代码 + +```go +package leetcode + +func minMoves(nums []int, limit int) int { + diff := make([]int, limit*2+2) // nums[i] <= limit, b+limit+1 is maximum limit+limit+1 + for j := 0; j < len(nums)/2; j++ { + a, b := min(nums[j], nums[len(nums)-j-1]), max(nums[j], nums[len(nums)-j-1]) + // using prefix sum: most interesting point, and is the key to reduce complexity + diff[2] += 2 + diff[a+1]-- + diff[a+b]-- + diff[a+b+1]++ + diff[b+limit+1]++ + } + cur, res := 0, len(nums) + for i := 2; i <= 2*limit; i++ { + cur += diff[i] + res = min(res, cur) + } + return res +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} +``` \ No newline at end of file diff --git a/leetcode/1690.Stone-Game-VII/1690. Stone Game VII.go b/leetcode/1690.Stone-Game-VII/1690. Stone Game VII.go new file mode 100644 index 000000000..c2ad93b4f --- /dev/null +++ b/leetcode/1690.Stone-Game-VII/1690. Stone Game VII.go @@ -0,0 +1,65 @@ +package leetcode + +// 解法一 优化空间版 DP +func stoneGameVII(stones []int) int { + n := len(stones) + sum := make([]int, n) + dp := make([]int, n) + for i, d := range stones { + sum[i] = d + } + for i := 1; i < n; i++ { + for j := 0; j+i < n; j++ { + if (n-i)%2 == 1 { + d0 := dp[j] + sum[j] + d1 := dp[j+1] + sum[j+1] + if d0 > d1 { + dp[j] = d0 + } else { + dp[j] = d1 + } + } else { + d0 := dp[j] - sum[j] + d1 := dp[j+1] - sum[j+1] + if d0 < d1 { + dp[j] = d0 + } else { + dp[j] = d1 + } + } + sum[j] = sum[j] + stones[i+j] + } + } + return dp[0] +} + +// 解法二 常规 DP +func stoneGameVII1(stones []int) int { + prefixSum := make([]int, len(stones)) + for i := 0; i < len(stones); i++ { + if i == 0 { + prefixSum[i] = stones[i] + } else { + prefixSum[i] = prefixSum[i-1] + stones[i] + } + } + dp := make([][]int, len(stones)) + for i := range dp { + dp[i] = make([]int, len(stones)) + dp[i][i] = 0 + } + n := len(stones) + for l := 2; l <= n; l++ { + for i := 0; i+l <= n; i++ { + dp[i][i+l-1] = max(prefixSum[i+l-1]-prefixSum[i+1]+stones[i+1]-dp[i+1][i+l-1], prefixSum[i+l-2]-prefixSum[i]+stones[i]-dp[i][i+l-2]) + } + } + return dp[0][n-1] +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} diff --git a/leetcode/1690.Stone-Game-VII/1690. Stone Game VII_test.go b/leetcode/1690.Stone-Game-VII/1690. Stone Game VII_test.go new file mode 100644 index 000000000..60656ac91 --- /dev/null +++ b/leetcode/1690.Stone-Game-VII/1690. Stone Game VII_test.go @@ -0,0 +1,47 @@ +package leetcode + +import ( + "fmt" + "testing" +) + +type question1690 struct { + para1690 + ans1690 +} + +// para 是参数 +// one 代表第一个参数 +type para1690 struct { + stones []int +} + +// ans 是答案 +// one 代表第一个答案 +type ans1690 struct { + one int +} + +func Test_Problem1690(t *testing.T) { + + qs := []question1690{ + + { + para1690{[]int{5, 3, 1, 4, 2}}, + ans1690{6}, + }, + + { + para1690{[]int{7, 90, 5, 1, 100, 10, 10, 2}}, + ans1690{122}, + }, + } + + fmt.Printf("------------------------Leetcode Problem 1690------------------------\n") + + for _, q := range qs { + _, p := q.ans1690, q.para1690 + fmt.Printf("【input】:%v 【output】:%v\n", p, stoneGameVII(p.stones)) + } + fmt.Printf("\n\n\n") +} diff --git a/leetcode/1690.Stone-Game-VII/README.md b/leetcode/1690.Stone-Game-VII/README.md new file mode 100644 index 000000000..c7b618acf --- /dev/null +++ b/leetcode/1690.Stone-Game-VII/README.md @@ -0,0 +1,130 @@ +# [1690. Stone Game VII](https://leetcode.com/problems/stone-game-vii/) + +## 题目 + +Alice and Bob take turns playing a game, with **Alice starting first**. + +There are `n` stones arranged in a row. On each player's turn, they can **remove** either the leftmost stone or the rightmost stone from the row and receive points equal to the **sum** of the remaining stones' values in the row. The winner is the one with the higher score when there are no stones left to remove. + +Bob found that he will always lose this game (poor Bob, he always loses), so he decided to **minimize the score's difference**. Alice's goal is to **maximize the difference** in the score. + +Given an array of integers `stones` where `stones[i]` represents the value of the `ith` stone **from the left**, return *the **difference** in Alice and Bob's score if they both play **optimally**.* + +**Example 1:** + +``` +Input: stones = [5,3,1,4,2] +Output: 6 +Explanation: +- Alice removes 2 and gets 5 + 3 + 1 + 4 = 13 points. Alice = 13, Bob = 0, stones = [5,3,1,4]. +- Bob removes 5 and gets 3 + 1 + 4 = 8 points. Alice = 13, Bob = 8, stones = [3,1,4]. +- Alice removes 3 and gets 1 + 4 = 5 points. Alice = 18, Bob = 8, stones = [1,4]. +- Bob removes 1 and gets 4 points. Alice = 18, Bob = 12, stones = [4]. +- Alice removes 4 and gets 0 points. Alice = 18, Bob = 12, stones = []. +The score difference is 18 - 12 = 6. +``` + +**Example 2:** + +``` +Input: stones = [7,90,5,1,100,10,10,2] +Output: 122 +``` + +**Constraints:** + +- `n == stones.length` +- `2 <= n <= 1000` +- `1 <= stones[i] <= 1000` + +## 题目大意 + +石子游戏中,爱丽丝和鲍勃轮流进行自己的回合,爱丽丝先开始 。有 n 块石子排成一排。每个玩家的回合中,可以从行中 移除 最左边的石头或最右边的石头,并获得与该行中剩余石头值之 和 相等的得分。当没有石头可移除时,得分较高者获胜。鲍勃发现他总是输掉游戏(可怜的鲍勃,他总是输),所以他决定尽力 减小得分的差值 。爱丽丝的目标是最大限度地 扩大得分的差值 。 + +给你一个整数数组 stones ,其中 stones[i] 表示 从左边开始 的第 i 个石头的值,如果爱丽丝和鲍勃都 发挥出最佳水平 ,请返回他们 得分的差值 。 + +## 解题思路 + +- 首先考虑 Bob 缩小分值差距意味着什么,意味着他想让他和 Alice 相对分数最小。Bob 已经明确肯定是输,所以他的分数一定比 Alice 小,那么 Bob - Alice 分数相减一定是负数。相对分数越小,意味着差值越大。负数越大,差值越小。-50 和 -10,-10 数值大,相差小。所以 Bob 的操作是让相对分数越大。Alice 的目的也是这样,要让 Alice - Bob 的相对分数越大,这里是正数的越大。综上,两者的目的相同,都是让相对分数最大化。 +- 定义 `dp[i][j]` 代表在当前 `stone[i ~ j]` 区间内能获得的最大分差。状态转移方程为: + + ```go + dp[i][j] = max( + sum(i + 1, j) - dp[i + 1][j], // 这一局取走 `stone[i]`,获得 sum(i + 1, j) 分数,再减去剩下对手能获得的分数,即是此局能获得的最大分差。 + sum(i, j - 1) - dp[i][j - 1] // 这一局取走 `stone[j]`,获得 sum(i, j - 1) 分数,再减去剩下对手能获得的分数,即是此局能获得的最大分差。 + ) + ``` + + 计算 `sum(i + 1, j) = stone[i + 1] + stone[i + 2] + …… + stone[j]` 利用前缀和计算区间和。 + +- 解法二是正常思路解答出来的代码。解法一是压缩了 DP 数组,在 DP 状态转移的时候,生成下一个 `dp[j]` 实际上是有规律的。利用这个规律可以少存一维数据,压缩空间。解法一的代码直接写出来,比较难想。先写出解法二的代码,然后找到递推规律,优化空间压缩一维,再写出解法一的代码。 + +## 代码 + +```go +package leetcode + +// 解法一 优化空间版 DP +func stoneGameVII(stones []int) int { + n := len(stones) + sum := make([]int, n) + dp := make([]int, n) + for i, d := range stones { + sum[i] = d + } + for i := 1; i < n; i++ { + for j := 0; j+i < n; j++ { + if (n-i)%2 == 1 { + d0 := dp[j] + sum[j] + d1 := dp[j+1] + sum[j+1] + if d0 > d1 { + dp[j] = d0 + } else { + dp[j] = d1 + } + } else { + d0 := dp[j] - sum[j] + d1 := dp[j+1] - sum[j+1] + if d0 < d1 { + dp[j] = d0 + } else { + dp[j] = d1 + } + } + sum[j] = sum[j] + stones[i+j] + } + } + return dp[0] +} + +// 解法二 常规 DP +func stoneGameVII1(stones []int) int { + prefixSum := make([]int, len(stones)) + for i := 0; i < len(stones); i++ { + if i == 0 { + prefixSum[i] = stones[i] + } else { + prefixSum[i] = prefixSum[i-1] + stones[i] + } + } + dp := make([][]int, len(stones)) + for i := range dp { + dp[i] = make([]int, len(stones)) + dp[i][i] = 0 + } + n := len(stones) + for l := 2; l <= n; l++ { + for i := 0; i+l <= n; i++ { + dp[i][i+l-1] = max(prefixSum[i+l-1]-prefixSum[i+1]+stones[i+1]-dp[i+1][i+l-1], prefixSum[i+l-2]-prefixSum[i]+stones[i]-dp[i][i+l-2]) + } + } + return dp[0][n-1] +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} +``` \ No newline at end of file diff --git a/website/content/ChapterFour/1674.Minimum-Moves-to-Make-Array-Complementary.md b/website/content/ChapterFour/1674.Minimum-Moves-to-Make-Array-Complementary.md new file mode 100644 index 000000000..8e565e10a --- /dev/null +++ b/website/content/ChapterFour/1674.Minimum-Moves-to-Make-Array-Complementary.md @@ -0,0 +1,97 @@ +# [1674. Minimum Moves to Make Array Complementary](https://leetcode.com/problems/minimum-moves-to-make-array-complementary/) + +## 题目 + +You are given an integer array `nums` of **even** length `n` and an integer `limit`. In one move, you can replace any integer from `nums` with another integer between `1` and `limit`, inclusive. + +The array `nums` is **complementary** if for all indices `i` (**0-indexed**), `nums[i] + nums[n - 1 - i]` equals the same number. For example, the array `[1,2,3,4]` is complementary because for all indices `i`, `nums[i] + nums[n - 1 - i] = 5`. + +Return the ***minimum** number of moves required to make* `nums` ***complementary***. + +**Example 1:** + +``` +Input: nums = [1,2,4,3], limit = 4 +Output: 1 +Explanation: In 1 move, you can change nums to [1,2,2,3] (underlined elements are changed). +nums[0] + nums[3] = 1 + 3 = 4. +nums[1] + nums[2] = 2 + 2 = 4. +nums[2] + nums[1] = 2 + 2 = 4. +nums[3] + nums[0] = 3 + 1 = 4. +Therefore, nums[i] + nums[n-1-i] = 4 for every i, so nums is complementary. +``` + +**Example 2:** + +``` +Input: nums = [1,2,2,1], limit = 2 +Output: 2 +Explanation: In 2 moves, you can change nums to [2,2,2,2]. You cannot change any number to 3 since 3 > limit. +``` + +**Example 3:** + +``` +Input: nums = [1,2,1,2], limit = 2 +Output: 0 +Explanation: nums is already complementary. +``` + +**Constraints:** + +- `n == nums.length` +- `2 <= n <= 105` +- `1 <= nums[i] <= limit <= 105` +- `n` is even. + +## 题目大意 + +给你一个长度为 偶数 n 的整数数组 nums 和一个整数 limit 。每一次操作,你可以将 nums 中的任何整数替换为 1 到 limit 之间的另一个整数。 + +如果对于所有下标 i(下标从 0 开始),nums[i] + nums[n - 1 - i] 都等于同一个数,则数组 nums 是 互补的 。例如,数组 [1,2,3,4] 是互补的,因为对于所有下标 i ,nums[i] + nums[n - 1 - i] = 5 。 + +返回使数组 互补 的 最少 操作次数。 + +## 解题思路 + +- 这一题考察的是差分数组。通过分析题意,可以得出,针对每一个 `sum` 的取值范围是 `[2, 2* limt]`,定义 `a = min(nums[i], nums[n - i - 1])`,`b = max(nums[i], nums[n - i - 1])`,在这个区间内,又可以细分成 5 个区间,`[2, a + 1)`,`[a + 1, a + b)`,`[a + b + 1, a + b + 1)`,`[a + b + 1, b + limit + 1)`,`[b + limit + 1, 2 * limit)`,在这 5 个区间内使得数组互补的最小操作次数分别是 `2(减少 a, 减少 b)`,`1(减少 b)`,`0(不用操作)`,`1(增大 a)`,`+2(增大 a, 增大 b)`,换个表达方式,按照扫描线从左往右扫描,在这 5 个区间内使得数组互补的最小操作次数叠加变化分别是 `+2(减少 a, 减少 b)`,`-1(减少 a)`,`-1(不用操作)`,`+1(增大 a)`,`+1(增大 a, 增大 b)`,利用这前后两个区间的关系,就可以构造一个差分数组。差分数组反应的是前后两者的关系。如果想求得 0 ~ n 的总关系,只需要求一次前缀和即可。 +- 这道题要求输出最少的操作次数,所以利用差分数组 + 前缀和,累加前缀和的同时维护最小值。从左往右扫描完一遍以后,输出最小值即可。 + +## 代码 + +```go +package leetcode + +func minMoves(nums []int, limit int) int { + diff := make([]int, limit*2+2) // nums[i] <= limit, b+limit+1 is maximum limit+limit+1 + for j := 0; j < len(nums)/2; j++ { + a, b := min(nums[j], nums[len(nums)-j-1]), max(nums[j], nums[len(nums)-j-1]) + // using prefix sum: most interesting point, and is the key to reduce complexity + diff[2] += 2 + diff[a+1]-- + diff[a+b]-- + diff[a+b+1]++ + diff[b+limit+1]++ + } + cur, res := 0, len(nums) + for i := 2; i <= 2*limit; i++ { + cur += diff[i] + res = min(res, cur) + } + return res +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} +``` \ No newline at end of file diff --git a/website/content/ChapterFour/1690.Stone-Game-VII.md b/website/content/ChapterFour/1690.Stone-Game-VII.md new file mode 100644 index 000000000..c7b618acf --- /dev/null +++ b/website/content/ChapterFour/1690.Stone-Game-VII.md @@ -0,0 +1,130 @@ +# [1690. Stone Game VII](https://leetcode.com/problems/stone-game-vii/) + +## 题目 + +Alice and Bob take turns playing a game, with **Alice starting first**. + +There are `n` stones arranged in a row. On each player's turn, they can **remove** either the leftmost stone or the rightmost stone from the row and receive points equal to the **sum** of the remaining stones' values in the row. The winner is the one with the higher score when there are no stones left to remove. + +Bob found that he will always lose this game (poor Bob, he always loses), so he decided to **minimize the score's difference**. Alice's goal is to **maximize the difference** in the score. + +Given an array of integers `stones` where `stones[i]` represents the value of the `ith` stone **from the left**, return *the **difference** in Alice and Bob's score if they both play **optimally**.* + +**Example 1:** + +``` +Input: stones = [5,3,1,4,2] +Output: 6 +Explanation: +- Alice removes 2 and gets 5 + 3 + 1 + 4 = 13 points. Alice = 13, Bob = 0, stones = [5,3,1,4]. +- Bob removes 5 and gets 3 + 1 + 4 = 8 points. Alice = 13, Bob = 8, stones = [3,1,4]. +- Alice removes 3 and gets 1 + 4 = 5 points. Alice = 18, Bob = 8, stones = [1,4]. +- Bob removes 1 and gets 4 points. Alice = 18, Bob = 12, stones = [4]. +- Alice removes 4 and gets 0 points. Alice = 18, Bob = 12, stones = []. +The score difference is 18 - 12 = 6. +``` + +**Example 2:** + +``` +Input: stones = [7,90,5,1,100,10,10,2] +Output: 122 +``` + +**Constraints:** + +- `n == stones.length` +- `2 <= n <= 1000` +- `1 <= stones[i] <= 1000` + +## 题目大意 + +石子游戏中,爱丽丝和鲍勃轮流进行自己的回合,爱丽丝先开始 。有 n 块石子排成一排。每个玩家的回合中,可以从行中 移除 最左边的石头或最右边的石头,并获得与该行中剩余石头值之 和 相等的得分。当没有石头可移除时,得分较高者获胜。鲍勃发现他总是输掉游戏(可怜的鲍勃,他总是输),所以他决定尽力 减小得分的差值 。爱丽丝的目标是最大限度地 扩大得分的差值 。 + +给你一个整数数组 stones ,其中 stones[i] 表示 从左边开始 的第 i 个石头的值,如果爱丽丝和鲍勃都 发挥出最佳水平 ,请返回他们 得分的差值 。 + +## 解题思路 + +- 首先考虑 Bob 缩小分值差距意味着什么,意味着他想让他和 Alice 相对分数最小。Bob 已经明确肯定是输,所以他的分数一定比 Alice 小,那么 Bob - Alice 分数相减一定是负数。相对分数越小,意味着差值越大。负数越大,差值越小。-50 和 -10,-10 数值大,相差小。所以 Bob 的操作是让相对分数越大。Alice 的目的也是这样,要让 Alice - Bob 的相对分数越大,这里是正数的越大。综上,两者的目的相同,都是让相对分数最大化。 +- 定义 `dp[i][j]` 代表在当前 `stone[i ~ j]` 区间内能获得的最大分差。状态转移方程为: + + ```go + dp[i][j] = max( + sum(i + 1, j) - dp[i + 1][j], // 这一局取走 `stone[i]`,获得 sum(i + 1, j) 分数,再减去剩下对手能获得的分数,即是此局能获得的最大分差。 + sum(i, j - 1) - dp[i][j - 1] // 这一局取走 `stone[j]`,获得 sum(i, j - 1) 分数,再减去剩下对手能获得的分数,即是此局能获得的最大分差。 + ) + ``` + + 计算 `sum(i + 1, j) = stone[i + 1] + stone[i + 2] + …… + stone[j]` 利用前缀和计算区间和。 + +- 解法二是正常思路解答出来的代码。解法一是压缩了 DP 数组,在 DP 状态转移的时候,生成下一个 `dp[j]` 实际上是有规律的。利用这个规律可以少存一维数据,压缩空间。解法一的代码直接写出来,比较难想。先写出解法二的代码,然后找到递推规律,优化空间压缩一维,再写出解法一的代码。 + +## 代码 + +```go +package leetcode + +// 解法一 优化空间版 DP +func stoneGameVII(stones []int) int { + n := len(stones) + sum := make([]int, n) + dp := make([]int, n) + for i, d := range stones { + sum[i] = d + } + for i := 1; i < n; i++ { + for j := 0; j+i < n; j++ { + if (n-i)%2 == 1 { + d0 := dp[j] + sum[j] + d1 := dp[j+1] + sum[j+1] + if d0 > d1 { + dp[j] = d0 + } else { + dp[j] = d1 + } + } else { + d0 := dp[j] - sum[j] + d1 := dp[j+1] - sum[j+1] + if d0 < d1 { + dp[j] = d0 + } else { + dp[j] = d1 + } + } + sum[j] = sum[j] + stones[i+j] + } + } + return dp[0] +} + +// 解法二 常规 DP +func stoneGameVII1(stones []int) int { + prefixSum := make([]int, len(stones)) + for i := 0; i < len(stones); i++ { + if i == 0 { + prefixSum[i] = stones[i] + } else { + prefixSum[i] = prefixSum[i-1] + stones[i] + } + } + dp := make([][]int, len(stones)) + for i := range dp { + dp[i] = make([]int, len(stones)) + dp[i][i] = 0 + } + n := len(stones) + for l := 2; l <= n; l++ { + for i := 0; i+l <= n; i++ { + dp[i][i+l-1] = max(prefixSum[i+l-1]-prefixSum[i+1]+stones[i+1]-dp[i+1][i+l-1], prefixSum[i+l-2]-prefixSum[i]+stones[i]-dp[i][i+l-2]) + } + } + return dp[0][n-1] +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} +``` \ No newline at end of file diff --git a/website/content/menu/index.md b/website/content/menu/index.md index f7be90947..78ca8d480 100644 --- a/website/content/menu/index.md +++ b/website/content/menu/index.md @@ -570,6 +570,7 @@ headless: true - [1670.Design-Front-Middle-Back-Queue]({{< relref "/ChapterFour/1670.Design-Front-Middle-Back-Queue.md" >}}) - [1672.Richest-Customer-Wealth]({{< relref "/ChapterFour/1672.Richest-Customer-Wealth.md" >}}) - [1673.Find-the-Most-Competitive-Subsequence]({{< relref "/ChapterFour/1673.Find-the-Most-Competitive-Subsequence.md" >}}) + - [1674.Minimum-Moves-to-Make-Array-Complementary]({{< relref "/ChapterFour/1674.Minimum-Moves-to-Make-Array-Complementary.md" >}}) - [1678.Goal-Parser-Interpretation]({{< relref "/ChapterFour/1678.Goal-Parser-Interpretation.md" >}}) - [1679.Max-Number-of-K-Sum-Pairs]({{< relref "/ChapterFour/1679.Max-Number-of-K-Sum-Pairs.md" >}}) - [1680.Concatenation-of-Consecutive-Binary-Numbers]({{< relref "/ChapterFour/1680.Concatenation-of-Consecutive-Binary-Numbers.md" >}}) @@ -578,4 +579,5 @@ headless: true - [1685.Sum-of-Absolute-Differences-in-a-Sorted-Array]({{< relref "/ChapterFour/1685.Sum-of-Absolute-Differences-in-a-Sorted-Array.md" >}}) - [1688.Count-of-Matches-in-Tournament]({{< relref "/ChapterFour/1688.Count-of-Matches-in-Tournament.md" >}}) - [1689.Partitioning-Into-Minimum-Number-Of-Deci-Binary-Numbers]({{< relref "/ChapterFour/1689.Partitioning-Into-Minimum-Number-Of-Deci-Binary-Numbers.md" >}}) + - [1690.Stone-Game-VII]({{< relref "/ChapterFour/1690.Stone-Game-VII.md" >}})