LeetCode 1723: 完成所有工作的最短时间

给你一个整数数组 jobs ,其中 jobs[i] 是完成第 i 项工作要花费的时间。

 请你将这些工作分配给 k 位工人。所有工作都应该分配给工人,且每项工作只能分配给一位工人。工人的 工作时间 是完成分配给他们的所有工作花费时间的总和。请你设计一套最佳的工作分配方案,使工人的 最大工作时间 得以 最小化 。

 返回分配方案中尽可能 最小 的 最大工作时间 。

 示例 1:

 输入:jobs = [3,2,3], k = 3

 输出:3

 解释:给每位工人分配一项工作,最大工作时间是 3 。

 示例 2:

 输入:jobs = [1,2,4,7,8], k = 2

 输出:11

 解释:按下述方式分配工作:

 1 号工人:1、2、8(工作时间 = 1 + 2 + 8 = 11)

 2 号工人:4、7(工作时间 = 4 + 7 = 11)

 最大工作时间是 11 。


一看是一个hard题目,心已经凉了半截,先试试朴素的方法求解。

感性地理解,如果要求将大任务和小任务都分配到人上,优先放入大任务更容易使得工作变得简单。
在任务分配过程中,优先分配工作量小的工作会使得工作量大的工作更有可能最后无法被分配。

  • 给每个人建立一个已分配任务的耗时统计数组time,index为人的编号,value已分配任务的总时长
  • 将工作按时间从大到小排序
  • 每次把当前最大的工作分配给当前总工作时间最少的工人

这种方法简单但不一定能得到最优解,但是对于某些case应该是能通过的。问了一下GPT,原来这个是贪心算法。

贪心算法(Greedy Algorithm)是一种在每一步选择中都采取当前状态下最好或最优的选择,从而希望导致结果是全局最好或最优的算法。

贪心算法的特征:

局部最优选择: 每一步都做出当前看起来最好的选择

不可取消: 一旦做出选择就不能反悔

不回溯: 不会根据后续结果调整之前的选择。

再分配任务的过程中

贪心选择: 每次都将当前工作分配给目前工作时间最少的工人

局部最优: 这种分配方式在当前时刻是最优的(让负载最轻的工人承担新工作)

不可回溯: 一旦工作被分配就不会重新调整

/// 解法:使用贪心算法求解(可能不是最优解)
class Solution {/// 计算完成所有工作的最短时间(贪心方法)/// - Parameters:///   - jobs: 工作数组,每个元素表示一个工作的耗时///   - k: 工人数量/// - Returns: 分配方案下的最大工作时间func minimumTimeRequired(_ jobs: [Int], _ k: Int) -> Int {// 记录每个工人的工作时间var time: [Int] = []// 初始化k个工人的工作时间为0for _ in 0..<k {time.append(0)}// 将工作按照耗时从大到小排序let sortJobs = jobs.sorted().reversed()// 贪心策略:每次将当前工作分配给工作时间最少的工人for job in sortJobs {let (index, _) = self.findMinValueAndIndex(time)time[index] += job}// 返回所有工人中的最大工作时间return time.max()!}/// 查找数组中的最小值及其索引/// - Parameter array: 输入数组/// - Returns: 包含最小值索引和值的元组private func findMinValueAndIndex(_ array: [Int]) -> (index: Int, value: Int) {guard var minValue = array.first else { return (0, 0) }var minIndex = 0// 遍历数组找出最小值和对应索引for (index, value) in array.enumerated() {if value < minValue {minValue = valueminIndex = index}}return (minIndex, minValue)}
}

跑了一下,一个62个用例,通过了52个测试用例,占比80%,确实不是最优解,但是也是一个比较好的思路。


思路2

答案一定在特定区间内

  • 下界:单个工作的最大耗时
  • 上界:所有工作的总耗时

在这个范围内寻找,一定能找到最终解。在范围内寻找,可以使用二分法不断缩小边界。

需要解决的核心问题是:在给定时间限制下,能否将所有工作分配给 k 个工人?

先假定给到第i个工人,然后看是否能满足条件,需要不断的递归调用,直到所有任务都分配完成。

/// 函数:判断在给定时间限制下是否能分配所有工作
/// - Parameters:
///   - jobs: 排序后的工作数组
///   - index: 当前处理的工作索引
///   - limit: 时间限制
///   - workload: 每个工人当前的工作量
/// - Returns: 是否能够成功分配所有工作
private func canDistribute(_ jobs: [Int], _ index: Int, _ limit: Int, _ workload: inout [Int]) -> Bool {// 第i项工作已经分配完成 -> 所有工作都已分配, if index >= jobs.count {return true}let currentJob = jobs[index]// 尝试将当前工作分配给每个工人,for i in 0..<workload.count {if workload[i] + currentJob <= limit {  // 当前工人可以接受这份工作workload[i] += currentJob           // 分配工作// 递归调用自己,继续分配下一份工作if canDistribute(jobs, index + 1, limit, &workload) {  return true}workload[i] -= currentJob           // 回溯:撤销分配}// 优化:如果当前工人未被分配工作,后续工人也不需要尝试if workload[i] == 0 {break}}return false
}

注意是尝试将当前工作分配给每个工人,并且分配不成功时会撤销分配,通过这种方式,会尝试把尝试把工作i给到每个工人身上,最大循环次数是  jobs.count * k 次,相当于穷举了。

虽然最大循环次数是jobs.count * k , 但是有一些优化策略可以帮助我们减少一些不必要的循环

工作排序

  • 将工作按照耗时从大到小排序
  • 大工作先分配可以提早触发限制条件

剪枝优化

  • 每个工人都是等价的,如果某个工人未被分配工作,后续工人也无需尝试

状态记录

  • 使用 `workload` 数组记录每个工人的当前工作量
  • 通过回溯维护状态的正确性,避免重复计算

有了核心判断方法,在加上二分查找,不断缩小边界,最终实现如下:

/// 解法:使用二分查找 + 回溯的方法求解
class Solution {/// 计算完成所有工作的最短时间/// - Parameters:///   - jobs: 工作数组,每个元素表示一个工作的耗时///   - k: 工人数量/// - Returns: 最优分配方案下的最大工作时间func minimumTimeRequired(_ jobs: [Int], _ k: Int) -> Int {// 对工作按耗时从大到小排序,有助于提早触发限制条件let sortedJobs = jobs.sorted(by: >)// 二分查找的下界:单个工作的最大耗时var left = sortedJobs[0]// 二分查找的上界:所有工作的总耗时var right = jobs.reduce(0, +)// 二分查找过程while left < right {let mid = left + (right - left) / 2// 记录每个工人的工作量var workload = Array(repeating: 0, count: k)// 判断是否能在mid时间限制内分配所有工作if canDistribute(sortedJobs, 0, mid, &workload) {right = mid    // 可以完成,尝试减小限制} else {left = mid + 1 // 不能完成,增加限制}}return left}/// 回溯函数:判断在给定时间限制下是否能分配所有工作/// - Parameters:///   - jobs: 排序后的工作数组///   - index: 当前处理的工作索引///   - limit: 时间限制///   - workload: 每个工人当前的工作量/// - Returns: 是否能够成功分配所有工作private func canDistribute(_ jobs: [Int], _ index: Int, _ limit: Int, _ workload: inout [Int]) -> Bool {// 基准情况:所有工作都已分配完成if index >= jobs.count {return true}let cur = jobs[index]// 尝试将当前工作分配给每个工人for i in 0..<workload.count {// 如果当前工人添加这份工作后未超过限制if workload[i] + cur <= limit {workload[i] += cur  // 分配工作// 递归分配下一份工作if canDistribute(jobs, index + 1, limit, &workload) {return true}workload[i] -= cur  // 回溯:撤销分配}// 优化:如果当前工人未被分配工作,后续工人也不需要尝试if workload[i] == 0 {break}}return false}
}

通过这种方式确实找到了最优解。

时间复杂度分析

  • 二分查找:O(log(sum)),其中 sum 是工作总时间 
  • 回溯过程:O(k^n),其中 n 是工作数量,k 是工人数量
  • 总体复杂度:O(log(sum) * k^n)

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

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

相关文章

JDK8新特性之Steam流

这里写目录标题 一、Stream流概述1.1、传统写法1.2、Stream写法1.3、Stream流操作分类 二、Stream流获取方式2.1、根据Collection获取2.2、通过Stream的of方法 三、Stream常用方法介绍3.1、forEach3.2、count3.3、filter3.4、limit3.5、skip3.6、map3.7、sorted3.8、distinct3.…

split方法

在编程中&#xff0c;split 方法通常用于将字符串按照指定的分隔符拆分成多个部分&#xff0c;并返回一个包含拆分结果的列表&#xff08;或数组&#xff09;。不同编程语言中的 split 方法语法略有不同&#xff0c;但核心功能相似。以下是常见语言中的用法&#xff1a; ​1. P…

深入理解 x86 汇编中的符号扩展指令:从 CBW 到 CDQ 的全解析

引入 在汇编语言的世界里&#xff0c;数据宽度的转换是一项基础却至关重要的操作。尤其是在处理有符号数时&#xff0c;符号扩展&#xff08;Sign Extension&#xff09;作为保持数值符号一致性的核心技术&#xff0c;直接影响着运算结果的正确性。本文将聚焦 x86 架构中最常用…

计算机基础知识(第五篇)

计算机基础知识&#xff08;第五篇&#xff09; 架构演化与维护 软件架构的演化和定义 软件架构的演化和维护就是对架构进行修改和完善的过程&#xff0c;目的就是为了使软件能够适应环境的变化而进行的纠错性修改和完善性修改等&#xff0c;是一个不断迭代的过程&#xff0…

前端开发三剑客:HTML5+CSS3+ES6

在前端开发领域&#xff0c;HTML、CSS和JavaScript构成了构建网页与Web应用的核心基础。随着技术标准的不断演进&#xff0c;HTML5、CSS3以及ES6&#xff08;ECMAScript 2015及后续版本&#xff09;带来了诸多新特性与语法优化&#xff0c;极大地提升了开发效率和用户体验。本文…

c++ 头文件

目录 防止头文件重复包含 头文件的作用 如何让程序的多个 .cpp 文件之间共享全局变量&#xff08;可能是 int、结构体、数组、指针、类对象&#xff09;? 防止头文件重复包含 为什么要防止头问件重复包含&#xff1f; 当然一般也不会把变量定义放到头问件&#xff0c;那为…

深入解析 JavaScript 中 var、let、const 的核心区别与实践应用

一、历史背景与语法基础 JavaScript 作为动态弱类型语言&#xff0c;变量声明机制经历了从 ES5 到 ES6 的重大变革。在 ES5 及更早版本中&#xff0c;var 是唯一的变量声明方式&#xff0c;而 ES6&#xff08;2015 年&#xff09;引入了 let 和 const&#xff0c;旨在解决 var…

【Linux庖丁解牛】—自定义shell的编写!

1. 打印命令行提示符 在我们使用系统提供的shell时&#xff0c;每次都会打印出一行字符串&#xff0c;这其实就是命令行提示符&#xff0c;那我们自定义的shell当然也需要这一行字符串。 这一行字符串包含用户名&#xff0c;主机名&#xff0c;当前工作路径&#xff0c;所以&a…

应用案例 | 设备分布广, 现场维护难? 宏集Cogent DataHub助力分布式锅炉远程运维, 让现场变“透明”

在日本&#xff0c;能源利用与环保问题再次成为社会关注的焦点。越来越多的工业用户开始寻求更高效、可持续的方式来运营设备、管理能源。而作为一家专注于节能与自动化系统集成的企业&#xff0c;日本大阪的TESS工程公司给出了一个值得借鉴的答案。 01 锅炉远程监控难题如何破…

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术&#xff0c;它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton)&#xff1a;由层级结构的骨头组成&#xff0c;类似于人体骨骼蒙皮 (Mesh Skinning)&#xff1a;将模型网格顶点绑定到骨骼上&#xff0c;使骨骼移动…

jdk同时安装多个版本并自由切换

一、安装不同版本的JDK 二、配置环境变量&#xff08;多版本JDK&#xff09; 1. 新建版本专用环境变量&#xff08;用于切换&#xff09; 操作位置&#xff1a;系统变量 > 新建 变量名&#xff1a;JAVA_HOME_1.8 变量值&#xff1a;JDK 8安装路径变量名&#xff1a;JAVA1…

java中装饰模式

目录 一 装饰模式案例说明 1.1 说明 1.2 代码 1.2.1 定义数据服务接口 1.2.2 定义基础数据库服务实现 1.2.3 日志装饰器 1.2.4 缓存装饰器 1.2.5 主程序调用 1.3 装饰模式的特点 一 装饰模式案例说明 1.1 说明 本案例是&#xff1a;数据查询增加缓存&#xff0c;使用…

【论文阅读】YOLOv8在单目下视多车目标检测中的应用

Application of YOLOv8 in monocular downward multiple Car Target detection​​​​​ 原文真离谱&#xff0c;文章都不全还发上来 引言 自动驾驶技术是21世纪最重要的技术发展之一&#xff0c;有望彻底改变交通安全和效率。任何自动驾驶系统的核心都依赖于通过精确物体检…

在uni-app中如何从Options API迁移到Composition API?

uni-app 从 Options API 迁移到 Composition API 的详细指南 一、迁移前的准备 升级环境&#xff1a; 确保 HBuilderX 版本 ≥ 3.2.0项目 uni-app 版本 ≥ 3.0.0 了解 Composition API 基础&#xff1a; 响应式系统&#xff1a;ref、reactive生命周期钩子&#xff1a;onMount…

408第一季 - 数据结构 - 图

图的概念 完全图 无向图的完全图可以这么想&#xff1a;如果有4个点&#xff0c;每个点都会连向3个点&#xff0c;每个点也都会有来回的边&#xff0c;所以除以2 有向图就不用除以2 连通分量 不多解释 极大连通子图的意思就是让你把所有连起来的都圈出来 强连通图和强连通…

31.2linux中Regmap的API驱动icm20608实验(编程)_csdn

regmap 框架就讲解就是上一个文章&#xff0c;接下来学习编写的 icm20608 驱动改为 regmap 框架。 icm20608 驱动我们在之前的文章就已经编写了&#xff01; 因为之前已经对icm20608的设备树进行了修改&#xff0c;所以大家可以看到之前的文章&#xff01;当然这里我们还是带领…

Vue速查手册

Vue速查手册 CSS deep用法 使用父class进行限定&#xff0c;控制影响范围&#xff1a; <template><el-input class"my-input" /> </template><style scoped> /* Vue 3 推荐写法 */ .my-input :deep(.el-input__inner) {background-color…

振动力学:无阻尼多自由度系统(受迫振动)

本文从频域分析和时域分析揭示系统的运动特性&#xff0c;并给出系统在一般形式激励下的响应。主要讨论如下问题&#xff1a;频域分析、频响函数矩阵、反共振、振型叠加法等。 根据文章1中的式(1.7)&#xff0c;可知无阻尼受迫振动的初值问题为&#xff1a; M u ( t ) K u …

真实案例分享,Augment Code和Cursor那个比较好用?

你有没有遇到过这种情况&#xff1f;明明知道自己想要什么&#xff0c;写出来的提示词却让AI完全理解错了。 让AI翻译一篇文章&#xff0c;结果生成的中文不伦不类&#xff0c;机器僵硬&#xff0c;词汇不同&#xff0c;鸡同鸭讲。中国人看不懂&#xff0c;美国人表示耸肩。就…

zotero及其插件安装

zotero官网&#xff1a;Zotero | Your personal research assistant zotero中文社区&#xff1a;快速开始 | Zotero 中文社区 插件下载镜像地址&#xff1a;Zotero 插件商店 | Zotero 中文社区 翻译&#xff1a;Translate for Zotero 接入腾讯翻译API&#xff1a;总览 - 控制…