Hard
题目描述
有一个披萨,被切成了 3n 块大小不同的扇形。你和你的朋友们将按以下规则取披萨:
- 你选择任意一块披萨。
- 你的朋友 Alice 将选择你所选择的披萨逆时针方向的下一块披萨。
- 你的朋友 Bob 将选择你所选择的披萨顺时针方向的下一块披萨。
- 重复上述过程直到没有披萨剩余。
给你一个整数数组 slices ,表示按顺时针方向披萨块的大小。请你返回你能获得的披萨大小总和的最大值。
示例 1:
输入:slices = [1,2,3,4,5,6]
输出:10
解释:选择大小为 4 的披萨,Alice 和 Bob 分别选择大小为 3 和 5 的披萨。然后你选择大小为 6 的披萨,Alice 和 Bob 最后分别选择大小为 2 和 1 的披萨。总和 = 4 + 6。
示例 2:
输入:slices = [8,9,8,6,1,1]
输出:16
解释:每一轮都选择大小为 8 的披萨。如果你选择大小为 9 的披萨,你的伙伴们就会选择大小为 8 的披萨。
提示:
3 * n == slices.length1 <= slices.length <= 5001 <= slices[i] <= 1000
解题思路
这道题的关键是理解游戏规则的本质。通过分析可以发现,每当你选择一块披萨时,相邻的两块披萨都会被朋友拿走,这意味着你不能选择相邻的披萨块。
经过仔细分析,这个问题等价于:在一个环形数组中选择 n 个不相邻的元素,使得它们的和最大。
由于数组是环形的(第一个和最后一个元素相邻),我们需要分两种情况讨论:
- 不选择第一个元素:在
slices[1:]中选择 n 个不相邻的元素 - 不选择最后一个元素:在
slices[:-1]中选择 n 个不相邻的元素
对于每种情况,我们使用动态规划来解决。定义 dp[i][j] 表示从前 i 个元素中选择 j 个不相邻元素的最大和。状态转移方程为:
dp[i][j] = max(dp[i-1][j], dp[i-2][j-1] + slices[i-1])
第一项表示不选择当前元素,第二项表示选择当前元素(因此不能选择前一个元素)。
代码实现
class Solution {
public:
int maxSizeSlices(vector<int>& slices) {
int n = slices.size() / 3;
// 情况1:不选择第一个元素
vector<int> arr1(slices.begin() + 1, slices.end());
// 情况2:不选择最后一个元素
vector<int> arr2(slices.begin(), slices.end() - 1);
return max(rob(arr1, n), rob(arr2, n));
}
private:
int rob(vector<int>& arr, int k) {
int m = arr.size();
if (k == 0) return 0;
if (m == 0) return 0;
vector<vector<int>> dp(m + 1, vector<int>(k + 1, 0));
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= min(i, k); j++) {
dp[i][j] = dp[i-1][j];
if (i >= 2) {
dp[i][j] = max(dp[i][j], dp[i-2][j-1] + arr[i-1]);
} else if (j == 1) {
dp[i][j] = max(dp[i][j], arr[i-1]);
}
}
}
return dp[m][k];
}
};
class Solution:
def maxSizeSlices(self, slices: List[int]) -> int:
n = len(slices) // 3
def rob(arr, k):
m = len(arr)
if k == 0 or m == 0:
return 0
dp = [[0] * (k + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, min(i, k) + 1):
dp[i][j] = dp[i-1][j]
if i >= 2:
dp[i][j] = max(dp[i][j], dp[i-2][j-1] + arr[i-1])
elif j == 1:
dp[i][j] = max(dp[i][j], arr[i-1])
return dp[m][k]
# 情况1:不选择第一个元素
case1 = rob(slices[1:], n)
# 情况2:不选择最后一个元素
case2 = rob(slices[:-1], n)
return max(case1, case2)
public class Solution {
public int MaxSizeSlices(int[] slices) {
int n = slices.Length / 3;
// 情况1:不选择第一个元素
int[] arr1 = new int[slices.Length - 1];
Array.Copy(slices, 1, arr1, 0, slices.Length - 1);
// 情况2:不选择最后一个元素
int[] arr2 = new int[slices.Length - 1];
Array.Copy(slices, 0, arr2, 0, slices.Length - 1);
return Math.Max(Rob(arr1, n), Rob(arr2, n));
}
private int Rob(int[] arr, int k) {
int m = arr.Length;
if (k == 0 || m == 0) return 0;
int[,] dp = new int[m + 1, k + 1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= Math.Min(i, k); j++) {
dp[i, j] = dp[i - 1, j];
if (i >= 2) {
dp[i, j] = Math.Max(dp[i, j], dp[i - 2, j - 1] + arr[i - 1]);
} else if (j == 1) {
dp[i, j] = Math.Max(dp[i, j], arr[i - 1]);
}
}
}
return dp[m, k];
}
}
var maxSizeSlices = function(slices) {
const n = slices.length / 3;
function rob(arr) {
const m = arr.length;
if (m === 0) return 0;
if (n === 1) return Math.max(...arr);
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n && j <= i; j++) {
dp[i][j] = dp[i-1][j];
if (i >= 2) {
dp[i][j] = Math.max(dp[i][j], dp[i-2][j-1] + arr[i-1]);
} else if (j === 1) {
dp[i][j] = Math.max(dp[i][j], arr[i-1]);
}
}
}
return dp[m][n];
}
const case1 = rob(slices.slice(0, -1));
const case2 = rob(slices.slice(1));
return Math.max(case1, case2);
};
复杂度分析
| 复杂度类型 | 复杂度 |
|---|---|
| 时间复杂度 | O(n²) |
| 空间复杂度 | O(n²) |
其中 n 是披萨块数的三分之一。动态规划需要填充 O(n) × O(n) 的状态表,每个状态的计算时间为 O(1)。