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
繁體地址,請注明出處:http://hk.pswp.cn/web/82964.shtml
英文地址,請注明出處:http://en.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;總覽 - 控制…