LeetCode1402.做菜顺序
LeetCode1402.做菜顺序【hard】。
题目描述
一个厨师收集了他 n 道菜的满意程度 satisfaction ,这个厨师做出每道菜的时间都是 1 单位时间。
一道菜的 「 like-time 系数 」定义为烹饪这道菜结束的时间(包含之前每道菜所花费的时间)乘以这道菜的满意程度,也就是 time[i]*satisfaction[i] 。
返回厨师在准备了一定数量的菜肴后可以获得的最大 like-time 系数总和。
你可以任意顺序安排做菜的顺序,你也可以选择放弃做某些菜来获得更大的总和。
输入输出示例
示例 1:
输入:satisfaction = [-1,-8,0,5,-9]
输出:14
解释:去掉第二道和最后一道菜,最大的 like-time 系数和为 (-11 + 02 + 5*3 = 14) 。每道菜都需要花费 1 单位时间完成。
示例 2:
输入:satisfaction = [4,3,2]
输出:20
解释:可以按照任意顺序做菜 (21 + 32 + 4*3 = 20)
示例 3:
输入:satisfaction = [-1,-4,-5]
输出:0
解释:大家都不喜欢这些菜,所以不做任何菜就可以获得最大的 like-time 系数。
提示:
- n == satisfaction.length
- 1 <= n <= 500
- -1000 <= satisfaction[i] <= 1000
题目分析
①想要得到最大like-time系数总和,要满足以下几个原则:
- 满意度为负数的菜尽量不做;
- 为了让满意度为正数的菜获得更多 like-time 系数,可能要适当地做一些满意度为负数的菜;
- 满意度越高的菜越要晚做。
②为了满足以上原则,首先必要对数组进行排序,满意度越大的菜越往后排;
③每一次的决策都依赖于当前状态,都影响着后续状态,是很经典的动态规划问题(此处暂不讲解动态规划概念);
④进行状态定义:明确变量,第x次对第y道菜做出决策,对于每道菜可以选或不选,此时可以转化为 [0 - 1 背包] 问题;用i代表对第i道菜抉择,j代表进行第j次决策,用dp[i][j]
代表在前i道菜中进行了j次选择,即在前i道菜中选择了j道时的最大 like-time 系数总和;
⑤分析状态转移方程:前i道菜中选择了j道菜时,有两种情况。第i道菜在第j次选择时被选择了,第i道菜在第j次选择时没有被选择。
- 第i道菜在第j次选择时被选择了,意味着在前i-1道菜中选择了j-1道菜,同时加上第i道菜的like-time系数,即
j * satisfaction[i-1]
(满意度数组下标从0开始)。可得公式dp[i][j] = dp[i-1][j-1] + j * satisfaction[i-1]
。 - 第i道菜在第j次选择时没有被选择,意味着第j次选择发生在前i-1道菜之间,即在前i-1道菜中选择了j道,并且 i-1 >= j ,即 i > j。可得公式
dp[i][j] = dp[i-1][j], i > j
。
题解
题解一:动态规划
C#
1 | public int MaxSatisfaction(int[] satisfaction) |
Tips:为什么定义的是
int[][] dp = new int[n + 1][]
而不是int[][] dp = new int[n][]
,是一个小技巧,计算从索引1开始,避免在计算i-1或j-1时还要考虑索引小于0的情况。
题解二:前缀和
一个很妙的解法,涉及到前缀和的解法。
知题解一分析,满意度越高的菜应该越晚做,同时兼做一些满意度为负的菜来增加时间系数。此处有一个简单的数学原理:i * n
等于i + i……,n个i相加。我们在排序后,可以从满意度最高的菜开始考虑,这道菜每晚一轮做,相当于增加了一次这道菜的满意值,而n道菜每晚一轮做,相当于把这些菜的满意度都累加一遍。
此时,我们就可以利用一个值sum,来代表选择做这道菜时的收益,等于在此之后选择做的菜满意度累加值加上当前菜的满意度,如果大于0,说明选择做这道菜对于之后的选择来说收益为正,那么我们就可以选择做这道菜,由此可得解法如下:
1 | public int MaxSatisfaction(int[] satisfaction) { |