力扣-621. 任務排程器

DawnTraveler發表於2024-06-22

1.題目

題目地址(621. 任務排程器 - 力扣(LeetCode))

https://leetcode.cn/problems/task-scheduler/

題目描述

給你一個用字元陣列 tasks 表示的 CPU 需要執行的任務列表,用字母 A 到 Z 表示,以及一個冷卻時間 n。每個週期或時間間隔允許完成一項任務。任務可以按任何順序完成,但有一個限制:兩個 相同種類 的任務之間必須有長度為 n 的冷卻時間。

返回完成所有任務所需要的 最短時間間隔

示例 1:

輸入:tasks = ["A","A","A","B","B","B"], n = 2
輸出:8
解釋:A -> B -> (待命) -> A -> B -> (待命) -> A -> B
     在本示例中,兩個相同型別任務之間必須間隔長度為 n = 2 的冷卻時間,而執行一個任務只需要一個單位時間,所以中間出現了(待命)狀態。 

示例 2:

輸入:tasks = ["A","A","A","B","B","B"], n = 0
輸出:6
解釋:在這種情況下,任何大小為 6 的排列都可以滿足要求,因為 n = 0
["A","A","A","B","B","B"]
["A","B","A","B","A","B"]
["B","B","B","A","A","A"]
...
諸如此類

示例 3:

輸入:tasks = ["A","A","A","A","A","A","B","C","D","E","F","G"], n = 2
輸出:16
解釋:一種可能的解決方案是:
     A -> B -> C -> A -> D -> E -> A -> F -> G -> A -> (待命) -> (待命) -> A -> (待命) -> (待命) -> A

提示:

  • 1 <= tasks.length <= 104
  • tasks[i] 是大寫英文字母
  • 0 <= n <= 100

2.題解

2.1 貪心演算法 + 優先順序佇列

思路

這題的關鍵思路是我們需要明白具體的貪心策略:
當處於某一個時間點,這裡有若干個未被冷卻的任務等待執行,我們應該優先執行那些剩餘次數較多的任務
因為其餘剩餘次數較少的任務,我們可以穿插到其他的時間間隔進行執行,因為剩餘次數少,需要插空的位置少,更容易完成,
而那些剩餘次數多的任務,如果放到後面執行,可能就會面對只剩下這類任務的情況,我們執行一個該類任務,必須等待一整個時間間隔n什麼都不能做

所以我們的貪心策略:優先執行當前未處於冷卻狀態,且剩餘執行次數較多的任務
後面一個比較好解決,使用優先順序佇列(大根堆)即可解決,我們如何解決前面一個限制呢?

我們發現我們如果是以一個冷卻週期n來劃分任務執行,那麼假設這個冷卻週期我執行了若干任務,並執行一個從優先順序佇列中排除一個;
並在這個冷卻週期完畢後(指第一個任務,中間其他任務尚未完成冷卻),將所有本週期執行過的,被排除的任務全部裝回優先順序佇列,並執行下一個週期;
我們可能有一個疑問,此時優先順序佇列中不是所有任務都冷卻了(上一個週期執行的後幾個任務都未冷卻),我們直接放回開啟新週期可以嗎?

答案是可以的,在下一個週期中,我們還是按,有這麼幾種可能:
1.這裡執行的任務上個冷卻週期我沒執行過(由於上一週期任務執行,部分任務執行次數減少後小於該任務,導致該任務優先順序上升),那麼自然沒有冷卻問題的
(關鍵!!!)2.這裡執行的任務上個冷卻週期我執行過(但是根據上一週期中優先順序高於你的任務,你們的剩餘次數同時-1,其實這一週期還是按次序還是排在你前面的,等他執行完,你也就冷卻好了!!!)

所以我們就可以採用一個冷卻週期一輪迴的方式來解決這個問題,一個輪迴中:
1.執行n+1個任務(自己執行的一次 + 冷卻時間的n次),並排除優先順序佇列
2.執行完畢後,將這些任務放回優先順序佇列
3.更新總執行時間

程式碼

  • 語言支援:C++

C++ Code:

class Solution {
public:
    int leastInterval(std::vector<char>& tasks, int n) {
        // 統計任務出現頻率
        unordered_map<char, int> taskFreq;
        for(char ch : tasks){
            if(taskFreq.count(ch)){
                taskFreq[ch]++;
            }else{
                taskFreq.emplace(ch, 1);
            }
        }

        // 使用優先順序佇列-維護剩餘次數從大到小排列(大根堆)
        priority_queue<int> maxheap;
        for(auto p : taskFreq){
            maxheap.push(p.second);
        }

        int ans = 0;
        // 開始時間流動,每次迴圈一個時間冷卻時間
        while(!maxheap.empty()){
            vector<int> temp;
            // 執行當前剩餘次數最多的任務(一個週期:自己執行的一個間隔 + 冷卻室家n)
            for(int i = 0; i < n + 1; i++){
                if(!maxheap.empty()){
                    temp.push_back(maxheap.top() - 1);
                    maxheap.pop();
                }
            }
            // 執行完一輪後,將未完成的任務放回優先順序佇列中
            for(int tmp : temp){
                if(tmp > 0){
                    maxheap.push(tmp);
                }
            }

            // 更新完成當前任務總的時間間隔
            // 如果棧為空,說明最後temp中的值均為0,所有任務執行完畢,加上這一部分即可
            if(maxheap.empty()){
                ans += temp.size();
            }else{
                ans += n + 1;
            }
        }
        return ans;
    }
};

複雜度分析

  • 時間複雜度:'o(w* log K),其中'N 是任務的總數量,'K 是任務種類的數量。
    對於每個任務操作,堆的操作(插入和刪除)是“o(log K)”的。
  • 空間複雜度:‘’o(k)”,主要用於儲存任務頻率的雜湊表和優先順序佇列。

相關文章