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

news2024/11/17 18:27:58

给你一个下标从 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,二分查找:

给你一个数组,对于1 <= i < len(nums)i可以有以下操作

  • nums[i]--nums[i-1]++
  • nums[i] > 0

对于这个条件,我们应该得到这样的理解:

  • 前方的较小数可以接受后方较大数多余的数字

可能这句话有些晦涩难懂,下面举一个例子具体分析

设nums = [2,3,7,1,6]

由对前三个数进行操作,则我们可以得到的最小最大值为4

怎么做到的捏?我们来一步步走

[2,3,7]

[3,2,7]

[4,1,7]

[4,2,6]

[4,3,5]

[4,4,4]

一步步下来,我们发现,前方的较小的2和3承接了来自后方的7中的数,最终使得整个数组都整体变小了

2承载了最终答案4中的,来自于7中的两个1

3承载了最终答案4中的,来自于7中的一个1

由此我们可以由局部推广到整体,我们只需要检查数组在小数承载大数的基础上,是否可以全部都不大于k.

那么要检查的数从哪里来?

以下2种都可以

  • 思路1,按照题目要求,从0到 10^9,开始二分查找。
  • 思路2,遍历一次数组,从数组的最小值到数组中的最大值开始二分。
class Solution {

    /// 检查数字k能否满足承载需求
    /// - Parameters:
    ///   - nums: 原始数组
    ///   - k: 本次检查的k值
    /// - Returns:
    /// true: 数组遍历完成,并且数组内的值可以承载,表示k是最大值中的一个,但不一定是最小的最大值;
    /// false: 数组内已经发现比k更大的值,后续遍历无意义
    func check(_ nums: [Int], k: Int) -> Bool {
        //前方的数字还可以帮我们后方的大数承载多少数字
        var extra = 0
        for value in nums {
            if (value <= k) { //当前值小于目标值,可以接受 k-value的承载
                extra += k - value
            } else {
                //当前值大于目标值,承载量 减去对应的差值 value-k
                extra -= value - k
                if extra < 0 { // extra < 0,表示到第i个位置的最大值已经>k,后续已经无需再比对了
                    return false
                }
            }
        }
        return true

    }

    func minimizeArrayValue(_ nums: [Int]) -> Int {

        var left = 0
        var right = 10 * 10000 * 10000

        // 二分答案,二分范围从0-10^9中寻找答案,
        // mid是本次查找预设的一个答案,检验这个答案是否满足要求
        // 如果满足check条件,说明可以继续向下查找,缩小右边界
        // 如果不满足check条件,把左边界加1,
        // 最终左右边界相等时,一定是左边界-1是不满足check条件,左边界及右侧都满足check条件
        while left < right {
            let mid = left + (right - left) / 2
            let canExtra = check(nums, k: mid)
            if canExtra {
                right = mid
            } else {
                left = mid + 1
            }
        }
        return left
    }
}

 时间复杂度是O(N*logN),空间复杂度为O(1),


进阶思路2,分类讨论:

削峰填谷,整体考虑,遇到第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).

总结来看, 分类讨论的这种算法效率最高,代码最简洁。

路漫漫其修远兮,吾将上下求索。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/133772.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

程序的环境与预处理 程序的编译与链接

目录 1.程序的翻译环境和执行环境 ​编辑 2.编译链接 运行环境 3.预处理 预定义符号 #define #与## 带副作用的宏参数 宏和函数的对比 命名约定 ​编辑 #undef​编辑 命令行定义 ​编辑 条件编译 文件包含 嵌套文件包含 4.其他预处理指令 1.程序的翻译环境和…

新年伊始,和大家聊聊鲜枣课堂的未来

大家好&#xff0c;我是小枣君。时间过得很快&#xff0c;转眼之间&#xff0c;2022年已经结束了。回首这一年&#xff0c;感觉自己一直都在忙&#xff0c;却想不起来到底忙了些什么。这一年&#xff0c;我的生活和工作节奏&#xff0c;一直都是混乱的。这里面&#xff0c;既有…

罗振宇2023“时间的朋友”跨年演讲原版PPT(附下载)

省时查报告-专业、及时、全面的行研报告库省时查方案-专业、及时、全面的营销策划方案库【免费下载】2022年11月份热门报告盘点2023年&#xff0c;如何科学制定年度规划&#xff1f;《底层逻辑》高清配图清华大学256页PPT元宇宙研究报告.pdf&#xff08;附下载链接&#xff09;…

软件测试[用例篇]

一. 回顾测试用例 1.测试用例基本要素 测试用例&#xff08;Test Case&#xff09;是为了实施测试而向被测试的系统提供的一组集合。 这组集合包含&#xff1a;测试环境、操作步骤、测试数据、预期结果等要素。 2.测试用例好处 测试用例可以提高测试效率&#xff08;可以减…

省时省力,高速收费站无线组网解决方案

一、行业背景随着我国高速公路里程数的不断增加&#xff0c;科技水平的不断进步&#xff0c;智能化的高速公路收费站趋势在不断的加强。例如&#xff1b;高速公路收费站智能备份系统&#xff0c;通常情况下收费站、路段分中心和省联网中心之间是需要传输收费数据记录流水、清账…

【1801. 积压订单中的订单总数】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给你一个二维整数数组 orders &#xff0c;其中每个 orders[i] [pricei, amounti, orderTypei] 表示有 amounti 笔类型为 orderTypei 、价格为 pricei 的订单。 订单类型 orderTypei 可以分为两种&…

关于el-time-picker使用错误的记录

之前在紧急参与一个PC管理后台的项目&#xff0c;项目的基础架子是花裤衩大佬的vue-element-admin()vue2版本),。其中有一个需求是列表数据中数据回显时候&#xff0c;有关时间部分的数据在回显/编辑的情况下&#xff0c;提交时获取的值有问题。虽然后面解决了&#xff0c;但还…

2023年要来了。顺便分享过来后我的学开车经历

你好呀&#xff0c;读者朋友们&#xff01;我是你们的老朋友 zhen guo时光如梭&#xff0c;转眼间我这边再有1个来小时就2023年了&#xff0c;因时差&#xff0c;很多看到这里的读者朋友应该都已经进入2023年。2022年再也回不去了&#xff0c;就像曾经过去的每一年那样&#xf…

【王道操作系统】2.3.1 进程的同步与互斥

进程的同步与互斥 文章目录进程的同步与互斥1.进程同步2.进程互斥1.进程同步 同步也称为直接制约关系在多道程序环境下&#xff0c;进程是并发执行的&#xff0c;不同进程之间存在着不同的相互制约关系。为了协调进程之间的相互制约关系,如等待、传递信息等&#xff0c;引入了…

IP协议重点总结(附实例)

文章目录前言一、IP地址1.1 概念1.2 作用1.3 格式1.4 组成1.5 分类二、NAT地址转换2.1 作用2.2 转换过程2.3 NAPT端口映射2.4 现实中的栗子&#xff08;以博主的手机为例&#xff09;2.4.1 连无线WLAN的情况2.4.2 用流量上网2.5 NAT的缺陷三、子网掩码3.1 格式3.2 作用3.3 计算…

windows安装IIS服务

安装ASP的环境IIS 1、使用快捷键 【Win X】 打开系统功能菜单&#xff0c;选择【程序和功能】 2、进入【程序和功能】界面管理后&#xff0c;点击【启用或关闭windows功能】。 3、然后保证以下勾选&#xff0c;其他的默认就行&#xff0c;点击确定。如图&#xff1a; 4、出现…

vueJs中的watch与watchEffect函数

前言有时&#xff0c;我们需要在状态变化时执行一些副作用,比如:监听路由状态,更改DOM,或是根据异步操作的结果去修改另一处的状态这个时候,就需要用到监听器在组合式API中,就可以使用watch函数在每次响应式状态发生变化时触发回调函数01使用watch监视refwatch:监听某个属性的变…

2023.01/1801. 积压订单中的订单总数

1801. 积压订单中的订单总数 题意: 给你一个二维整数数组 orders &#xff0c;其中每个 orders[i] [pricei, amounti, orderTypei] 表示有 amounti 笔类型为 orderTypei 、价格为 pricei 的订单。 订单类型 orderTypei 可以分为两种&#xff1a; 0 表示这是一批采购订单 buy …

3D打印:FDM打印使用CURA4.13.1版本配置

一、前言 今天是2023年1月1日&#xff0c;新年阳历的第一天&#xff0c;在整理CSDN和写年度计划&#xff0c;对2022的总结&#xff0c;就像写一篇博客来分享一下我2022年积累的最多的一项经验&#xff0c;就是使用3D打印机&#xff0c;在2022年我先后入手了3台3d打印机&#x…

聊聊数字化转型是个啥

“国有企业首要的职责&#xff0c;就是实现国有资产保值增值。这是衡量国企工作优劣的关键&#xff01;” ——李克强 如果你开了一家制衣厂&#xff0c;雇佣了10个员工买了10台缝纫机&#xff0c;假设一天可以生产100件衣服。 做老板的你想要提高这家工厂的生产数量&#xff0…

禾元生物冲刺科创板上市:累计亏损超4亿元,贝达药业为主要股东

12月30日&#xff0c;武汉禾元生物科技股份有限公司&#xff08;下称“禾元生物”&#xff09;在上海证券交易所递交招股书&#xff0c;准备在科创板上市。本次冲刺上市&#xff0c;禾元生物计划募资35.02亿元&#xff0c;将用于植物源重组人血清白蛋白产业化基地建设项目、新药…

更多的可能

1986年12月&#xff0c;路遥的《平凡的世界》出版了&#xff0c;1992年11月17日路遥去世了&#xff0c;享年43岁&#xff0c;距今30年了……人的一生常常是白驹过隙&#xff0c;忽然而已&#xff0c;人们也常常用星空里的流星比喻&#xff0c;细细想来&#xff0c;这还算是夸张…

IDEA集成Gitee,超简单

1. 在IDEA下载Gitee插件 file-Settings-Plugins搜索Gitee&#xff0c;并且install这样IDEA就集成了Gitee 2. 从远程仓库拉取项目 如果打开IDEA已经有项目了&#xff0c;那就先file-close退出&#xff0c;回到这个初始化页面VCS&#xff1a;version control system版本控制系…

怎样提升go中的RSA解密速度2~3倍

背景 我们的业务中大量的使用了RSA加密后的数据包&#xff0c;每个数据包大概17-30K不等&#xff0c;因为从php迁移到go,RSA解密业务是无法避免的&#xff0c;之前一直以为迁移后go的解密速度即使没有php快&#xff0c;但也应该相同因此使用了go中x509.ParsePKCS1PrivateKey和…

公务员国考省考小白需知

文章目录&#xff1a; 一&#xff1a;分类 国考 省考 二&#xff1a;必备途径 1.相关网站 1.1 官网 1.2 机构 1.3 时事 1.4 资源 2.相关公主号 3.应用 三&#xff1a;相关需知 1.考试内容 2.老师选择 3.相关 4.公务员行政级别划分表 一&#xff1a;分类 国考…