2023-11-23
难度 困难
1402. 做菜顺序 - 力扣(LeetCode)
一个厨师收集了他
n
道菜的满意程度satisfaction
,这个厨师做出每道菜的时间都是 1 单位时间。一道菜的 「 like-time 系数 」定义为烹饪这道菜结束的时间(包含之前每道菜所花费的时间)乘以这道菜的满意程度,也就是
time[i]
*satisfaction[i]
。返回厨师在准备了一定数量的菜肴后可以获得的最大 like-time 系数 总和。
你可以按 任意 顺序安排做菜的顺序,你也可以选择放弃做某些菜来获得更大的总和。
示例 1:
输入:satisfaction = [-1,-8,0,5,-9] 输出:14 解释:去掉第二道和最后一道菜,最大的 like-time 系数和为 (-1*1 + 0*2 + 5*3 = 14) 。每道菜都需要花费 1 单位时间完成。示例 2:
输入:satisfaction = [4,3,2] 输出:20 解释:可以按照任意顺序做菜 (2*1 + 3*2 + 4*3 = 20)示例 3:
输入:satisfaction = [-1,-4,-5] 输出:0 解释:大家都不喜欢这些菜,所以不做任何菜就可以获得最大的 like-time 系数。
首先还是看题意吧,这题难得的容易理解,出题人语文这次学的不错,我们不妨换个说法,这里我要加入点私货了,鬼泣各位都玩过吧,鬼泣系列除了华丽的搓招最灵魂的一点就是评分系统 了,从D打到SSS,这个评价可不只是说你当前的分数倍率,还是你打的好不好看的标志
而从D到SSS实际上是7个评分倍率,结算的评分来自于你造成的伤害*当前倍率,你可以通过战斗提升倍率,在高倍率的加成下获得高分数(所以高手都喜欢在敌人丝血放大招吃伤害溢出分数)
假如说每一次攻击都可以提升一个评价,即当前分数的倍率,比如D评价下我们的评分倍率只有1.0,攻击之后变成C,也就是说下一个攻击的评分倍率是2.0,依次往后分别是3.0一直到SSS的7.0,虽然每次攻击都能提升倍率,但每次攻击可以造成的伤害都不一样(我规定的),那么如果能使用的仅有5招,并且每一招只能使用一次,按照什么顺序出招才能获得最高分数?
这是我们可用的所有招式的伤害值nums[-1,-8,0,5,-9],至于负数,就当是将所有攻击的伤害都减去一个同一个常数,你可以将伤害为负的攻击带入连招来给下一次攻击提升分数倍率,求可获取的最大分数,你也可以随意调整攻击顺序,也可以不使用其中某些招式
我们计算分数的时候,最好的结果一定是最大的数字加了最多次,也就是说最高的伤害*最高的倍率,其次是低一级的伤害乘以低一级的倍率,也可以把伤害为负数的攻击放在最前面,这样就能让它们乘以最小的倍率还能给下一次攻击提升倍率,但是如果负数过“大”,比如[ -2,1,2],纵使把-2放在第一位,结果是
-2*1+1*2+2*3=6
如果不带上-2得到的就是1*1+2*2=5
但如果第一个数是-10,结果就是
-10*1+1*2+2*3=-2
还不如不带,所以要考虑的就是要不要用一些小于0的数字换倍率
直接使用动态规划
nums=[-1,-8,0,5,-9]
这里我是用的是用 f[ n ]来表示仅使用 n 个数字时能获得的最大值,如果n个数字全都是小于0的,就直接取0,如果只有一个数字,那么一定是最大值,但如果最大值也小于0,不如不要直接等于0,所以f [ 1 ]就是nums中的最大值和0的较大数,因为后面除了最大值还需要取第二大值和第三大值,最好的方法应该就是排序了,给数组排个序
Arrays.sort(nums)
f[ 1 ] = max(nums[len-1],0)
然后f[ 2 ]就是当存在最多两个值时如何取最大值,f [ 2 ]的最大值就是 “最大的数*2+第二大的数*1”或者 f [ 1 ]
为什么候选是f [ 1 ]?因为如果第二大的数如果是个负数,可能会导致 “最大的数*2+第二大的数*1” 反而变得比f [ 1 ]还小,不如舍弃第二大的数,然后还是使用 第一大的数*1,也就是f [ 2-1 ]
public int maxSatisfaction(int[] satisfaction) {
int len = satisfaction.length;
Arrays.sort(satisfaction);//首先给数组排序
int[] f = new int[len+1];//因为我定义的f是指当前包含多少个数字时的结果
//最后要求的结果就是f[最大长度]
//而数组最大下标不能超过长度,所以数组要+1
f[1] = Math.max(satisfaction[len-1],0);
//如果最大值为负数f[1]还不如取0
f[2] = Math.max(satisfaction[len-1]*2+satisfaction[len-2],f[1]);//如果你把这个代码提交了要注释掉这一行,有的数组最大只到f[1]
//前两个数的最大值,如果 最大的数*2+第二大的数*2反而更小了,
//不如不要第二大的数,还是用 最大的数*1,也就是f[2-1]
int sum = satisfaction[len-1];
int sum2 = satisfaction[len-1];
//这里定义的sum和sum2都是最大的数,上面的f[1]和f[2]可以看到,
//第一次是 最大的数字*1,第二次是 最大的数字*2+第二大的数字*1
//然后第三次是 最大的数字*3+第二大的数字*2加第三大的数字*1
//所以以此类推,实际上第一次是最大的一个数
//然后第二次又加上了最大的前两个数
//然后又是最大的前三个数
for (int i = 2; i <= len; i++) {
sum2+=satisfaction[len-i];//注意这里的len-i其实是len+1-i-1,len+1是数组f的长度,len+1-i表示倒数第i个值,这里的i是f的i,f的i又是表示i个数,所以i是从1开始,而sum2的初始值已经是nums[len-1]了,所以最后再减去1等于len-i
//这里的sum2就是最大的前i个数字的和
//当然每个数字只加了一次
sum+=sum2;
//所以sum就是把每次的sum2加起来
//最后的结果就是上面的最大*3+第二*2+第三*1......
f[i] = Math.max(sum,f[i-1]);
//最后动态规划每次取sum和f[i-1],比较
}
return f[len];
}