给你一个披萨,它由 3n 块不同大小的部分组成,现在你和你的朋友们需要按照如下规则来分披萨:
你挑选 任意 一块披萨。
Alice 将会挑选你所选择的披萨逆时针方向的下一块披萨。
Bob 将会挑选你所选择的披萨顺时针方向的下一块披萨。
重复上述过程直到没有披萨剩下。
每一块披萨的大小按顺时针方向由循环数组 slices
表示。
请你返回你可以获得的披萨大小总和的最大值。
示例 1:
![](https://assets.leetcode-cn.com/aliyun-lc- upload/uploads/2020/03/21/sample_3_1723.png)
**输入:** slices = [1,2,3,4,5,6]
**输出:** 10
**解释:** 选择大小为 4 的披萨,Alice 和 Bob 分别挑选大小为 3 和 5 的披萨。然后你选择大小为 6 的披萨,Alice 和 Bob 分别挑选大小为 2 和 1 的披萨。你获得的披萨总大小为 4 + 6 = 10 。
示例 2:
![](https://assets.leetcode-cn.com/aliyun-lc- upload/uploads/2020/03/21/sample_4_1723.png)
**输入:** slices = [8,9,8,6,1,1]
**输出:** 16
**解释:** 两轮都选大小为 8 的披萨。如果你选择大小为 9 的披萨,你的朋友们就会选择大小为 8 的披萨,这种情况下你的总和不是最大的。
提示:
1 <= slices.length <= 500
slices.length % 3 == 0
1 <= slices[i] <= 1000
前言 本题可以转化成如下问题:
给一个长度为 3n 的环状序列,你可以在其中选择 n 个数,并且任意两个数不能相邻,求这 n 个数的最大值。
为什么可以这样转化呢?我们只需要证明,对于任意一种在长度为 3n 的环状序列上选择 n 个不相邻数的方法,都等价于一种题目中挑选披萨的方法。我们使用数学归纳法,证明如下:
当 n=1 时,我们在 3 个数中任选 1 个,由于这是一个环状序列,因此选取其中的任意一个数,剩余的两个数都会在选取的数的两侧,即如果有 3 块披萨,我们可以选取其中的任意一块;
当 n \geq 2 时,一定存在一个数 x,使得 x 的某一侧有连续两个数没有被选择(假设所有选中不相邻数的间隔为 1,即中间只有一个数没有被选择,那么总数为 2n,与总数为 3n 矛盾)。不失一般性,我们设 x 的左侧有连续两个数没有被选择,并且 x 的右侧至少有一个数没有没选择(否则 x 和其右侧的数就相邻了),即:
\cdots, ?, 0, 0, x, 0 ?, \cdots
其中 0 表示这个数没有被选择,? 表示这个数选择的情况未知(即我们不需要考虑,它可能被选择,也可能没有被选择)。我们删去 x 以及左右两侧的数,得到:
\cdots, ?, 0, ?, \cdots
将长度为 3n 的环状序列变成了长度为 3(n-1) 的环状序列,并且该环状序列中有 n-1 个数被选取,且任意两个被选取的数不相邻。对应到披萨上,相当于我们挑选了数 x 对应未知的披萨,而 Alice 和 Bob 挑选了相邻的两块披萨。这样我们将问题的规模从 n 减小至 n-1,通过数学归纳法得证。
同时,经过观察可发现,原方法选出的披萨一定都不相邻。结合上面的证明,命题的充分必要性得证。
因此,我们需要设计算法,在长度为 3n 的环状序列中选择 n 个不相邻的数,使得这 n 个数的和最大。
方法一:动态规划 动态规划的解决方法和 213. 打家劫舍 II 较为相似。
我们首先考虑该序列不是环状时的解决方法,即给定的是长度为 3n 的普通序列。我们可以用 dp}[i][j] 表示在前 i 个数中选择了 j 个不相邻的数的最大和:
当 i \lt 2 或 j = 0 时:
dp} = \begin{cases} 0, & j = 0 \ \textit{slices}[0], & i = 0, j = 1 \ \max(\textit{slices}[0], \textit{slices}[1]), & i = 1, j = 1 \ -\infty, & i \lt 2,j \ge 2 \end{cases}
当 i \ge 2 且 j \gt 0 时,dp}[i][j] 可以从两个位置转移而来:
取两者的最大值即为状态转移方程:
\textit{dp}[i][j] = \max(\textit{dp}[i - 2][j - 1] + \textit{slices}[i], \textit{dp}[i - 1][j])
当该序列是环状序列时,我们应该如何求解呢?我们可以发现,环状序列相较于普通序列,相当于添加了一个限制:普通序列中的第一个和最后一个数不能同时选。这样一来,我们只需要对普通序列进行两遍动态即可得到答案,第一遍动态规划中我们删去普通序列中的第一个数,表示我们不会选第一个数;第二遍动态规划中我们删去普通序列中的最后一个数,表示我们不会选最后一个数。将这两遍动态规划得到的结果去较大值,即为在环状序列上的答案。
[sol1-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution {public : int calculate (const vector<int >& slices) { int N = slices.size (), n = (N + 1 ) / 3 ; vector<vector<int >> dp (N, vector <int >(n + 1 , INT_MIN)); dp[0 ][0 ] = 0 ; dp[0 ][1 ] = slices[0 ]; dp[1 ][0 ] = 0 ; dp[1 ][1 ] = max (slices[0 ], slices[1 ]); for (int i = 2 ; i < N; i++) { dp[i][0 ] = 0 ; for (int j = 1 ; j <= n; j++) { dp[i][j] = max (dp[i - 1 ][j], dp[i - 2 ][j - 1 ] + slices[i]); } } return dp[N - 1 ][n]; } int maxSizeSlices (vector<int >& slices) { vector<int > v1 (slices.begin() + 1 , slices.end()) ; vector<int > v2 (slices.begin(), slices.end() - 1 ) ; int ans1 = calculate (v1); int ans2 = calculate (v2); return max (ans1, ans2); } };
[sol1-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public int maxSizeSlices (int [] slices) { int [] v1 = new int [slices.length - 1 ]; int [] v2 = new int [slices.length - 1 ]; System.arraycopy(slices, 1 , v1, 0 , slices.length - 1 ); System.arraycopy(slices, 0 , v2, 0 , slices.length - 1 ); int ans1 = calculate(v1); int ans2 = calculate(v2); return Math.max(ans1, ans2); } public int calculate (int [] slices) { int N = slices.length, n = (N + 1 ) / 3 ; int [][] dp = new int [N][n + 1 ]; for (int i = 0 ; i < N; i++) { Arrays.fill(dp[i], Integer.MIN_VALUE); } dp[0 ][0 ] = 0 ; dp[0 ][1 ] = slices[0 ]; dp[1 ][0 ] = 0 ; dp[1 ][1 ] = Math.max(slices[0 ], slices[1 ]); for (int i = 2 ; i < N; i++) { dp[i][0 ] = 0 ; for (int j = 1 ; j <= n; j++) { dp[i][j] = Math.max(dp[i - 1 ][j], dp[i - 2 ][j - 1 ] + slices[i]); } } return dp[N - 1 ][n]; } }
[sol1-C#] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class Solution { public int MaxSizeSlices (int [] slices ) { int [] v1 = new int [slices.Length - 1 ]; int [] v2 = new int [slices.Length - 1 ]; Array.Copy(slices, 1 , v1, 0 , slices.Length - 1 ); Array.Copy(slices, 0 , v2, 0 , slices.Length - 1 ); int ans1 = Calculate(v1); int ans2 = Calculate(v2); return Math.Max(ans1, ans2); } public int Calculate (int [] slices ) { int N = slices.Length, n = (N + 1 ) / 3 ; int [][] dp = new int [N][]; for (int i = 0 ; i < N; i++) { dp[i] = new int [n + 1 ]; Array.Fill(dp[i], int .MinValue); } dp[0 ][0 ] = 0 ; dp[0 ][1 ] = slices[0 ]; dp[1 ][0 ] = 0 ; dp[1 ][1 ] = Math.Max(slices[0 ], slices[1 ]); for (int i = 2 ; i < N; i++) { dp[i][0 ] = 0 ; for (int j = 1 ; j <= n; j++) { dp[i][j] = Math.Max(dp[i - 1 ][j], dp[i - 2 ][j - 1 ] + slices[i]); } } return dp[N - 1 ][n]; } }
[sol1-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func max (a, b int ) int { if a > b { return a } return b } func calculate (slices []int ) int { N, n := len (slices), (len (slices) + 1 ) / 3 dp := make ([][]int , N) for i := 0 ; i < N; i++ { dp[i] = make ([]int , n + 1 ) for j := 0 ; j <= n; j++ { dp[i][j] = -0x3f3f3f3f } } dp[0 ][0 ], dp[0 ][1 ], dp[1 ][0 ], dp[1 ][1 ] = 0 , slices[0 ], 0 , max(slices[0 ], slices[1 ]) for i := 2 ; i < N; i++ { dp[i][0 ] = 0 for j := 1 ; j <= n; j++ { dp[i][j] = max(dp[i - 1 ][j], dp[i - 2 ][j - 1 ] + slices[i]) } } return dp[N - 1 ][n] } func maxSizeSlices (slices []int ) int { return max(calculate(slices[1 :]), calculate(slices[:len (slices) - 1 ])) }
[sol1-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 int calculate (const int * slices, int slicesSize) { int N = slicesSize; int n = (N + 1 ) / 3 ; int dp[N][n + 1 ]; for (int i = 0 ; i < N; i++) { for (int j = 0 ; j <= n; j++) { dp[i][j] = INT_MIN; } } dp[0 ][0 ] = 0 ; dp[0 ][1 ] = slices[0 ]; dp[1 ][0 ] = 0 ; dp[1 ][1 ] = fmax(slices[0 ], slices[1 ]); for (int i = 2 ; i < N; i++) { dp[i][0 ] = 0 ; for (int j = 1 ; j <= n; j++) { dp[i][j] = fmax(dp[i - 1 ][j], dp[i - 2 ][j - 1 ] + slices[i]); } } return dp[N - 1 ][n]; } int maxSizeSlices (int * slices, int slicesSize) { int ans1 = calculate(slices + 1 , slicesSize - 1 ); int ans2 = calculate(slices, slicesSize - 1 ); return fmax(ans1, ans2); }
[sol1-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution : def maxSizeSlices (self, slices: List [int ] ) -> int : def calculate (slices ): N, n = len (slices), (len (slices) + 1 ) // 3 dp = [[-10 **9 for i in range (n + 1 )] for j in range (N)] dp[0 ][0 ], dp[0 ][1 ] = 0 , slices[0 ] dp[1 ][0 ], dp[1 ][1 ] = 0 , max (slices[0 ], slices[1 ]) for i in range (2 , N, 1 ): dp[i][0 ] = 0 for j in range (1 , n + 1 , 1 ): dp[i][j] = max (dp[i - 1 ][j], dp[i - 2 ][j - 1 ] + slices[i]) return dp[N - 1 ][n] v1 = slices[1 :] v2 = slices[0 :-1 ] ans1 = calculate(v1) ans2 = calculate(v2) return max (ans1, ans2)
[sol1-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var maxSizeSlices = function (slices ) { const v1 = slices.slice (1 ); const v2 = slices.slice (0 , slices.length - 1 ); const ans1 = calculate (v1); const ans2 = calculate (v2); return Math .max (ans1, ans2); }; const calculate = (slices ) => { const N = slices.length ; const n = Math .floor ((slices.length + 1 ) / 3 ); const dp = new Array (N).fill (0 ).map (() => new Array (n + 1 ).fill (-Infinity )); dp[0 ][0 ] = 0 , dp[0 ][1 ] = slices[0 ]; dp[1 ][0 ] = 0 , dp[1 ][1 ] = Math .max (slices[0 ], slices[1 ]); for (let i = 2 ; i < N; i++) { dp[i][0 ] = 0 ; for (let j = 1 ; j <= n; j++) { dp[i][j] = Math .max (dp[i - 1 ][j], dp[i - 2 ][j - 1 ] + slices[i]); } } return dp[N - 1 ][n]; };
复杂度分析