leetcode 2439. 最小化数组中的最大值
Posted 想名真难
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了leetcode 2439. 最小化数组中的最大值相关的知识,希望对你有一定的参考价值。
给你一个下标从 0 开始的数组 nums
,它含有 n
个非负整数。
每一步操作中,你需要:
- 选择一个满足
1 <= i < n
的整数i
,且nums[i] > 0
。 - 将
nums[i]
减 1 。 - 将
nums[i - 1]
加 1 。
你可以对数组执行 任意 次上述操作,请你返回可以得到的 nums
数组中 最大值 最小 为多少。
示例 1:
输入:nums = [3,7,1,6] 输出:5 解释: 一串最优操作是: 1. 选择 i = 1 ,nums 变为 [4,6,1,6] 。 2. 选择 i = 3 ,nums 变为 [4,6,2,5] 。 3. 选择 i = 1 ,nums 变为 [5,5,2,5] 。 nums 中最大值为 5 。无法得到比 5 更小的最大值。 所以我们返回 5 。
示例 2:
输入:nums = [10,1] 输出:10 解释: 最优解是不改动 nums ,10 是最大值,所以返回 10 。
说实话,没这个例子,我都不知道题干在说什么,😂😂😂
第一个眼看,没什么思路,那就先用题目的要求进行模拟。
按照题目要求,2个相邻的数字经过若干次计算之后,肯定满足 num[i-1] >= num[i],
- 如果2个数字的和为偶数, num[i-1] = num[i] = 平均数
- 如果2个数字的和是奇数的话,num[i-1] 肯定是较大的那个
- 比如2个数字为1和8,和为9,平均数为(1 + 8) / 2 = 4,剩下的较大的数字为5,将平均数赋值给num[i],剩下较大的数组赋值给num[i-1],
搞定了一次循环之后,不一定完成了最大值的调整,上面的一轮遍历,只是保证了相近的2个数字满足了条件,不能保证经过调整后所有的数字都满足条件。
- 比如[1,5,10], 经过上面的一轮模拟,变成了[3,3,10] -> [3,7,6],7还有调整的空间
- 在经过一轮遍历,由[3,7,6] -> [5,5,6], -> [5,6,5],
- 此时虽然肉眼已经知道了结果,但是不满足所有的数字都满足num[i-1] >= num[i],在经过一轮遍历,变成 [6,5,5], 此时才能让所有的数字都满足num[i-1] >= num[i],才是外层循环的真正结束条件。
经过上面的模拟,目前知道需要经过2层循环,内部循环是保证相邻的2个数字满足num[i-1] >= num[i],外层循环保证所有的数字都满足num[i-1] >= num[i]。
在写的过程中,感觉算法非常像冒泡排序算法,冒泡只是单纯的交换相邻值,而此算法在交换相邻值的同时,计算了平均值并进行赋值。
算法需要内外2层循环,O(n^2)的时间复杂度,放上去果然出现超时,不过计算结果是没问题的。
class Solution
func minimizeArrayValue(_ nums: [Int]) -> Int
var tempNums = nums
var maxValue = 0
var hasChange = false
// 按照题目要求模拟,后一个数字大于前一个数字,就计算2者的平均值,较大值给i-1,较小值给i
// 最差情况是对连续递增的数组,需要循环O(n^2),有点冒泡算法的感觉,每次循环都把较大值往前冒泡一次,执行到最后,最大值就在数组的第一个
// 比如[1,2,3,4],按照此算法进行模拟,
// 第一次循环, [2,1,3,4],[2,2,2,4],[2,2,3,3]
// 第二次循环, [2,3,2,3],[2,3,3,2]
// 第三次循环, [3,2,3,2],[3,3,2,2]
repeat
hasChange = false
for (i,value) in tempNums.enumerated()
if i>0
let preValue = tempNums[i-1]
if value > preValue
// midValue 相当于是向下取整了, 比如 (1 + 8) / 2 = 4, 另一个较大的数组通过减法算出
// 较小值给到i, 较大值给到i-1, 一步一步把最大值冒泡到数组第一个位置
let midValue = (value + preValue)/2
tempNums[i-1] = value + preValue - midValue
tempNums[i] = midValue
hasChange = true
while hasChange == true
maxValue = tempNums.first!
return maxValue
既然我们没有思路,那就看看其他人的。
进阶思路1:
削峰填谷,整体考虑,遇到第i个时,把第i个当成最后一个,把前i-1个都当成一个数字进行处理。计算前i个数字的平均值向上取整,此时相当于把前i个的山峰削减完成,前i个的最大值就是平均值向上取整与之前最大值的比较结果,然后继续第i+1个。
从 nums[0] 开始讨论:
- 如果数组中有 nums[0],那么最大值为 nums[0]。
- 再考虑 nums[1],
- 如果 nums[0]>=nums[1],num[1]是山谷,最大值还是 nums[0],不用处理
- 如果 nums[0]<nums[1],说明num[1]是一个山峰,则应该平均这两个数,平均后的最大值向上取整,即(nums[0]+num[1])/2向上取整,与之前的山峰进行比对更新
- 再考虑 nums[2],
- 如果前面算出的最大值 >= nums[2] ,num[2]就是一个山谷,最大值不变,不用处理;
- 如果前面算出的最大值 < nums[2] ,说明num[1]是一个山峰,那么需要平均这三个数向上取整,与之前的山峰进行对比更新。
- .....
- 对于任意一个num[i],
- 如果前面算出的最大值 >= nums[i] ,num[i]就是一个山谷,最大值不变,不用处理;
- 如果前面算出的最大值 < nums[i] ,说明num[i]是一个山峰,那么需要平均前i个数字向上取整,与之前的山峰进行对比更新。
以此类推直到最后一个数。
过程中的最大值为答案。
为什么要向上取整?
因为数组内的总和是不变的,并且都为整型,直接使用平均值计算出来的为浮点型,向上取整计算出来的才是平均后的最大值。
为什么计算出前i个数字平均值向上取整后还需要和之前的山峰进行对比?
因为前一个山峰可能是第3个值,后续的第4-10都是山谷,到第11时才又遇到山峰,此时计算出的前11个平均值可能小于第3个值。
怎么向上取整?
正常思路,计算好平均数,使用ceil函数取整
let avg = Double(sum)/Double(i+1)
result = max(result, Int(ceil(avg)))
牛B思路,先对sum+i,在计算除法,计算结果必定是向上取整的。
(sum+i)/(i+1)
为什么是除以i+1?
因为下表是i,从0开始计数,共有i+1个数字
class Solution
func minimizeArrayValue(_ nums: [Int]) -> Int
var sum = 0
var result = nums.first!
for (i,value) in nums.enumerated()
sum += value
if result < value
// 正常取整
// let avg = Double(sum)/Double(i+1)
// result = max(result, Int(ceil(avg)))
// 牛B取整
result = max(result, (sum+i)/(i+1))
return result
果然好的思路代码也简洁,只需要一次遍历, 时间复杂度为O(n), 空间复杂度为O(1).
以上是关于leetcode 2439. 最小化数组中的最大值的主要内容,如果未能解决你的问题,请参考以下文章
LeetCode1856.子数组最小乘积的最大值 Maximum Subarray Min-Product(Java)
Leetcode刷题100天(阿里云周赛)—最大数和最小数—day42