常用演算法之貪心演算法

爬蜥發表於2019-01-06

思路:求解問題時,總是選當前最好的選擇,不從整體上考慮。因而選用貪心演算法必須保證當前選的最好的必定是整體最好的

示例

分發餅乾

假設你是一位很棒的家長,想要給你的孩子們一些小餅乾。但是,每個孩子最多隻能給一塊餅乾。對每個孩子 i ,都有一個胃口值 gi ,這是能讓孩子們滿足胃口的餅乾的最小尺寸;並且每塊餅乾 j ,都有一個尺寸 sj 。如果 sj >= gi ,我們可以將這個餅乾 j 分配給孩子 i ,這個孩子會得到滿足。你的目標是儘可能滿足越多數量的孩子,並輸出這個最大數值。
假設輸入[1,2], [1,2,3],那麼輸出為2。分析如下

  • 要儘可能的滿足更多的小孩,那麼最小尺寸的餅乾應該分給最小胃口的那個人,這樣才不至於後面胃口大的小孩吃不到,兒胃口大的小孩吃小的肯定無法滿足。這種選擇恰好也是全域性最佳的選擇
public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        int i=0;
        int j=0;
        int num=0;
        while(i<g.length && j<s.length){
            if(s[j]>=g[i]){
                num++;
                i++;
                j++;
            }else{
                j++;
            }
        }
        return num;
    }
複製程式碼

任務排程器

給定一個用字元陣列表示的 CPU 需要執行的任務列表。其中包含使用大寫的 A - Z 字母表示的26 種不同種類的任務。任務可以以任意順序執行,並且每個任務都可以在 1 個單位時間內執行完。CPU 在任何一個單位時間內都可以執行一個任務,或者在待命狀態。

然而,兩個相同種類的任務之間必須有長度為 n 的冷卻時間,因此至少有連續 n 個單位時間內 CPU 在執行不同的任務,或者在待命狀態。

你需要計算完成所有任務所需要的最短時間。
假如輸入 tasks = ['A','A','A','B','B','B'], n = 2 輸出為8,執行順序: A -> B -> (待命) -> A -> B -> (待命) -> A -> B。分析如下

  • 為了使得整體時間最短,那麼冷卻時間肯定是最少的,因此要儘可能保證兩個相同的任務之間的執行間隔為n。換句話說就是貪心的選擇執行n個不一樣的任務,使得CPU能夠充分利用
  • 要選擇先執行的任務,得考慮如何使得當前選擇整體是最優的,加入隨便選擇一個任務A執行,當存在一個任務B它的任務數比選擇的任務數要多時,這意味著B之間少了一次執行A的機會,也就是增加了要冷卻的風險,因而每次選擇任務數最多的來執行
public int leastInterval(char[] tasks, int n) { 
        int[] taskArr=new int[26];
        for(char c:tasks){
            taskArr[c-'A']++;
        }
        int maxLength=n;
        int interval=0;
        while (havaTask(taskArr)){
            //先執行任務數最多的任務
            int top = getTopTaskNotExecute(taskArr,Collections.emptyList());
            interval++;
            List<Integer> list = new ArrayList<>();
            list.add(top);
            for (int i=0;i<maxLength;i++)
            {
                //貪心的選擇沒有執行過的‘n’個任務最多而且沒有執行過的任務
                Integer nextTop = getTopTaskNotExecute(taskArr, list);
                if (nextTop==-1){
                    maxLength=list.size()-1;
                    break;
                }
                interval++;
                list.add(nextTop);
            }
            if (list.size()-1!=n && havaTask(taskArr)){
                interval+=n-list.size()+1;
            }
        }
        return interval;
    }
    
    public boolean havaTask(int[] tasks){
        for(int i=0;i<tasks.length;i++){
            if(tasks[i]>0){
                return true;
            }
        }
        return false;
    }
    
    public Integer getTopTaskNotExecute(int[] tasks,List<Integer> executeTasks){
        int maxTaskNums=0;
        int maxNumsTaskIndex=-1;
        for(int i=0;i<tasks.length;i++){
            if(!executeTasks.contains(i) && tasks[i]>maxTaskNums){
                maxTaskNums=tasks[i];
                maxNumsTaskIndex=i;
            }
        }
        if(maxNumsTaskIndex!=-1){
          tasks[maxNumsTaskIndex]--;  
        }
        return maxNumsTaskIndex;
    }
}
複製程式碼

當然如果只要解決問題,可以直接計算出結果

還是使用貪心的策略來作為邏輯考慮的

  • 假設只有1個任務數最多,而且是k個,共需要至少間隔數為 k-1 個間隔,那麼間隔消耗時間為 n*(k-1),k個任務本身執行時間為 k,總共時間至少為 n*(k-1)+k
  • 假設最多的任務數一樣的不止1個有m個,當執行完一遍最多工數一個時,還有剩餘的 m-1 個任務,而且他們的數量肯定都是1,且各不相同,所以總共時間為 n*(k-1)+k+(m-1)=(n+1)*(k-1)+m
  • 上述假設只是從單個任務的最多量來看的,沒有考慮獨立的任務數,有多少個任務肯定至少要執行多少的時間,最終取最大值即可
public int leastInterval(char[] tasks, int n) { 
int[] taskArr=new int[26];
for(char c:tasks){
    taskArr[c-'A']++;
}
Arrays.sort(taskArr);
int m=0;
for(int i=25;i>-1;i--){
    if(taskArr[i]==taskArr[25]){
        m++;
    }else{
        break;
    }
}
return Math.max(tasks.length,(taskArr[25]-1)*(n+1)+m);
}
複製程式碼

附錄

貪心演算法思路

相關文章