3408. Design Task Manager | LeetCode | Swift

vc7-avatar-img
發佈於LeetCode
更新 發佈閱讀 11 分鐘

每日一題

日期

2025/9/18

前置

LeetCode 有引入 swift-collections 1.1.4 ,這篇就會用其中的 Heap 資料結構而不自己實作。

Ref: https://support.leetcode.com/hc/en-us/articles/360011833974-What-are-the-environments-for-the-programming-languages

經過多次改善、簡化後最快有到 118ms

raw-image

想法與資料結構

swift-collections 提供的 Heap 無法有效率地移除,像是需要使用 unordered 陣列、找到目標物、最後再重新生成 Heap 。時間複雜度和空間複雜度都可觀之外,寫出來的程式碼也變得複雜難讀。

於是根據題意: taskId 是唯一,這篇的資料結構就可以這樣應用:

  • HashMap: 維持 taskId: task pairs,可以幫助我們快速以 taskId 取得 task 。 (O(1))
  • Heap: 維持排序,無論是刪或加, task 都只進不出。當要進行 execTop() 過程,只要 pop 到過時的 task 時直接忽略即可。
class TaskManager {
// 用來 Heap 排序的容器,其中的元素只增不減。在 pop 時和 taskMap 對照當下 task 的合法性
private var tasks = Heap<Task>()
// 識別現有 Task 的 Map, [taskId: Task]
private var taskMap = [Int: Task]()

/* To be continue */
}

Task 構造

根據題意,

  • priority 相同時,taskId 較大者為大
struct Task: Comparable {
let userId: Int
let taskId: Int
let priority: Int

// MARK: Comparable

static func < (lhs: Task, rhs: Task) -> Bool {
guard lhs.priority != rhs.priority else {
return lhs.taskId < rhs.taskId
}

return lhs.priority < rhs.priority
}
}

新增的核心邏輯

init 裡面、edit 都會用到生成 Task ,插入 Heap, HashMap 的邏輯就集中到這裡

class TaskManager {
/* 省略 */

/// 生成 Task 並插入 HashMap 和 Heap
func add(_ userId: Int, _ taskId: Int, _ priority: Int) {
let task = Task(
userId: userId,
taskId: taskId,
priority: priority
)
tasks.insert(task)
taskMap[task.taskId] = task
}
/* 省略 */
}

初始化

走訪完美一個子陣列,直接呼叫 add 即可

class TaskManager {
/* 省略 */
init(_ tasks: [[Int]]) {
for task in tasks {
add(task[0], task[1], task[2])
}
}
/* 省略 */
}

編輯

在編輯的時候,依據前面提到的處理方式, Heap 裡的 task 只出不進。這邊找到對應的 task 、取用 userId 後新增一個新的 task 。

class TaskManager {
/* 省略 */
func edit(_ taskId: Int, _ newPriority: Int) {
guard let task = taskMap[taskId] else { return }
add(task.userId, taskId, newPriority)
}
/* 省略 */
}

移除

因為 Heap 裡的 task 只出不進,這邊就只需要把 HashMap 裡面 key 為 taskId 的值清除就好了

class TaskManager {
/* 省略 */
func rmv(_ taskId: Int) {
taskMap[taskId] = nil
}
/* 省略 */
}

回傳最大 task 的 userId

因為 Heap 裡的 task 只出不進,所以我們這邊有兩個步驟

  1. 呼叫 popMax 彈出一個 task
  2. 彈出的 task 有在 HashMap 裡,否則忽略, 繼續 1. 彈出下一個最大值
class TaskManager {
/* 省略 */
func execTop() -> Int {
while let top = tasks.popMax() { // 1.
if taskMap[top.taskId] == top { // 2.
taskMap[top.taskId] = nil
return top.userId
}
}
return -1
}
/* 省略 */
}

最終程式碼

class TaskManager {
/// 用來 Heap 排序的容器,其中的元素只增不減
private var tasks = Heap<Task>()
/// 識別現有 Task 的 Map, [taskId: Task]
private var taskMap = [Int: Task]()

init(_ tasks: [[Int]]) {
for task in tasks {
add(task[0], task[1], task[2])
}
}

/// 生成 Task 並插入 HashMap 和 Heap
func add(_ userId: Int, _ taskId: Int, _ priority: Int) {
let task = Task(
userId: userId,
taskId: taskId,
priority: priority
)
tasks.insert(task)
taskMap[task.taskId] = task
}

func edit(_ taskId: Int, _ newPriority: Int) {
guard let task = taskMap[taskId] else { return }
add(task.userId, taskId, newPriority)
}

/// 只移除 HashMap 裡面的元素
func rmv(_ taskId: Int) {
taskMap[taskId] = nil
}

func execTop() -> Int {
while let top = tasks.popMax() {
if taskMap[top.taskId] == top {
taskMap[top.taskId] = nil
return top.userId
}
}
return -1
}
}

struct Task: Comparable {
let userId: Int
let taskId: Int
let priority: Int

// MARK: Comparable

static func < (lhs: Task, rhs: Task) -> Bool {
guard lhs.priority != rhs.priority else {
return lhs.taskId < rhs.taskId
}

return lhs.priority < rhs.priority
}
}



留言
avatar-img
留言分享你的想法!
avatar-img
萱寫寫
2會員
18內容數
讀書心得、活動參加心得
萱寫寫的其他內容
2025/09/17
LeetCode 每日一題: 2025/09/17
2025/09/17
LeetCode 每日一題: 2025/09/17
2025/09/16
2025/09/16
2025/09/15
2025/09/15
看更多
你可能也想看
Thumbnail
利用文字紀錄,明確寫下自己的採購項目......
Thumbnail
利用文字紀錄,明確寫下自己的採購項目......
Thumbnail
這篇文章描述了作者從兼職開發轉為全職開發的過程,並分享了從混進學界指日可待的積極態度。作者也提及自己在專案製作與個人生活上的矛盾與感想,最後分享了專案管理和敏捷開發相關的文章與影片。
Thumbnail
這篇文章描述了作者從兼職開發轉為全職開發的過程,並分享了從混進學界指日可待的積極態度。作者也提及自己在專案製作與個人生活上的矛盾與感想,最後分享了專案管理和敏捷開發相關的文章與影片。
Thumbnail
列出一套完整的程式 程式設計有許多種方法,不過通常會先列出清單的再逐一執行,這樣會加快程式設計的速度。設計通常會採取順推的辦法。所以順推的程式設計方式就是經歷觀念溝通、系統分析、資料統合、權限管理、頻率與時間、後台管理、畫面設計等等階段後,將框架設計完了以後,先列出一套完整的程式,將所有使用者都確
Thumbnail
列出一套完整的程式 程式設計有許多種方法,不過通常會先列出清單的再逐一執行,這樣會加快程式設計的速度。設計通常會採取順推的辦法。所以順推的程式設計方式就是經歷觀念溝通、系統分析、資料統合、權限管理、頻率與時間、後台管理、畫面設計等等階段後,將框架設計完了以後,先列出一套完整的程式,將所有使用者都確
Thumbnail
題目敘述 題目會給我們一個定義好的類別和function介面,要求我們實作建構子和ping() function來滿足指定的需求。 RecentCounter類別的建構子 建構子應該初始化來電紀錄,內容為空(零筆資料) int ping(int t) t代表來電時刻,單位是毫秒m
Thumbnail
題目敘述 題目會給我們一個定義好的類別和function介面,要求我們實作建構子和ping() function來滿足指定的需求。 RecentCounter類別的建構子 建構子應該初始化來電紀錄,內容為空(零筆資料) int ping(int t) t代表來電時刻,單位是毫秒m
Thumbnail
1.設計與開發 1.1 精明管家系統之儀表板 portfolio 中各標的的持有數量歷史資料,累積的資料量已經逐漸變得太大,原本存在 firestore 同一個 collection 中。因此資料在運算操作績效時,預設期間是 YTD,故將資料拆成每年一個 collection,以加快報表產生速度
Thumbnail
1.設計與開發 1.1 精明管家系統之儀表板 portfolio 中各標的的持有數量歷史資料,累積的資料量已經逐漸變得太大,原本存在 firestore 同一個 collection 中。因此資料在運算操作績效時,預設期間是 YTD,故將資料拆成每年一個 collection,以加快報表產生速度
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News