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 。

2439. 最小化数组中的最大值


说实话,没这个例子,我都不知道题干在说什么,😂😂😂

第一个眼看,没什么思路,那就先用题目的要求进行模拟。

按照题目要求,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 2091. 从数组中移除最大值和最小值

Leetcode刷题100天(阿里云周赛)—最大数和最小数—day42

Leetcode刷题100天(阿里云周赛)—最大数和最小数—day42

Leetcode——数组中最大数对和的最小值

LeetCode 1877 数组中最大数对和的最小值[排序] HERODING的LeetCode之路