貪心算法練習

630. Course Schedule III

算法概論第七週


630. Course Schedule III — 題目鏈接

題目描述

There are n different online courses numbered from 1 to n. Each course has some duration(course length) t and closed on dth day. A course should be taken continuously for t days and must be finished before or on the dth day. You will start at the 1st day.

Given n online courses represented by pairs (t,d), your task is to find the maximal number of courses that can be taken.

Example:

Input: [[100, 200], [200, 1300], [1000, 1250], [2000, 3200]]
Output: 3

Explanation:
 
There're totally 4 courses, but you can take 3 courses at most:
First, take the 1st course, it costs 100 days so you will finish it on the 100th day, and ready to take the next course on the 101st day.
Second, take the 3rd course, it costs 1000 days so you will finish it on the 1100th day, and ready to take the next course on the 1101st day. 
Third, take the 2nd course, it costs 200 days so you will finish it on the 1300th day. 
The 4th course cannot be taken now, since you will finish it on the 3300th day, which exceeds the closed date.

Note:

1. The integer 1 <= d, t, n <= 10,000.
2. You can't take two courses simultaneously.

思路分析

1. 帶記錄的遞歸

  • 對於每一個課程,我們都有選(前提是不衝突)和不選兩種可能
  • 一旦我們選擇一門課程,我們當然是儘可能把它儘可能提前學完,因此,我們只需維護一個已選課程花費的時間和timetime,在判斷是否衝突的時候,看當前課程所需時間和timetime相加是否超過ddlddl即可
  • 爲了實現這樣的判斷,我們需要將課程按結束時間從小到達排序
  • 在遞歸選擇的過程中,用數組記錄,節約開銷
  • 時間複雜度和空間複雜度都是O(nd)O(n*d),在本題中顯然容易超時

2.替換策略的貪心算法

  • 考慮一種貪心算法,只需要遍歷一次所有課程(按結束時間排好序),如果一個課程與當前選擇方案不衝突,加進去;如果衝突,則將其與當前方案的所有課程持續時間最長者比較,如果它持續時間短,替換之
  • 證明這種方法有效
    • 首先,由於沒有設置課程起始時間,課程肯定是越早上完越好
    • 其次,用一個時間短的課程替換已選方案時間長的,肯定安全,而且肯定給後面的課程留下更多時間
    • 並且,替換者和被替換者肯定不相容,如果相容在前一步的判斷中就加進去了
  • 設答案是resres,那麼遍歷查找的開銷就是O(ncount)O(n*count),空間複雜度O(1)O(1)

3.優先隊列進行優化

  • 其實前一種思路我們可以進一步優化對當前方案中時間最長課程的查找
  • 對於c++中基於堆的priority_queue,時間複雜度是這樣的
    • push() push(x) 令x入隊,時間複雜度爲O(logN)O(log N), 其中 N 爲當前優先隊列中元素的個數
    • top() 獲得隊首元素(即堆頂元素),時間複雜度爲O(1)O(1)
    • pop() 令隊首元素(即堆頂元素)出隊,時間複雜度爲O(logN)O(log N)
    • empty() 檢測優先隊列是否爲空,返回 true則空,返回false 則非空。時間複雜度爲O(1)O(1)
    • size() 返回優先隊列內元素的個數,時間複雜度爲$ O(1)$
  • 這樣可以時總的時間複雜度降到O(nlog(n))O(nlog(n))

##代碼實現

遞歸版

(TLE)
因爲知道超時,所有我沒有實現,這是網上的版本

public class Solution {
    public int scheduleCourse(int[][] courses) {
        Arrays.sort(courses, (a, b) -> a[1] - b[1]);
        Integer[][] memo = new Integer[courses.length][courses[courses.length - 1][1] + 1];
        return schedule(courses, 0, 0, memo);
    }
    public int schedule(int[][] courses, int i, int time, Integer[][] memo) {
        if (i == courses.length)
            return 0;
        if (memo[i][time] != null)
            return memo[i][time];
        int taken = 0;
        if (time + courses[i][0] <= courses[i][1])
            taken = 1 + schedule(courses, i + 1, time + courses[i][0], memo);
        int not_taken = schedule(courses, i + 1, time, memo);
        memo[i][time] = Math.max(taken, not_taken);
        return memo[i][time];
    }
}

遍歷貪心版

(AC,打敗3%的AC代碼)

class Solution {
public:
    static bool mysort(vector<int>& a, vector<int>& b){
        return a[1] < b[1];
    }
    int scheduleCourse(vector<vector<int> >& courses) {
        sort(courses.begin(), courses.end(), mysort);
        int time = 0;
        int count = 0;
        int tolnum = courses.size();
        for(int i = 0; i < tolnum; i++){
            if(courses[i][0] + time <= courses[i][1]){
                time += courses[i][0];
                courses[count++] = courses[i];
            }
            else{
                int maxind = i;
                for(int j = 0; j < count; j++){
                    if(courses[maxind][0] < courses[j][0])
                        maxind = j;
                }
                if(maxind != i && courses[maxind][0] > courses[i][0]){
                    time -= courses[maxind][0] - courses[i][0];
                    courses[maxind] = courses[i];
                }
            }
        }
        return count;
    }
};

優先隊列貪心版

(AC,打敗97.3%的AC代碼)

class Solution {
public:
    static bool mysort(vector<int>& a, vector<int>& b){
        return a[1] < b[1];
    }
    int scheduleCourse(vector<vector<int> >& courses) {
        sort(courses.begin(), courses.end(), mysort);
        int time = 0;
        priority_queue<int> que;
        for(int i = 0; i < courses.size(); i++){
            if(courses[i][0] + time <= courses[i][1]){
                time += courses[i][0];
                que.push(courses[i][0]);
            }
            else{
                if(que.empty())
                    continue;
                if(que.top() > courses[i][0]){
                    time -= que.top() - courses[i][0];
                    que.pop();
                    que.push(courses[i][0]);  
                }
            }
        }
        return que.size();
    }
};

優先隊列的使用

  1. 普通方法:
priority_queue<int> q;    
//通過操作,按照元素從大到小的順序出隊
//參數爲容器內容物
priority_queue<int,vector<int>, greater<int> > q; 
//通過操作,按照元素從小到大的順序出隊
//其中,第一個爲容器內容物,第二個參數爲隊列使用的容器類型。第三個參數爲比較結構體。

2、自定義優先級:

struct cmp {     
  operator bool ()(int x, int y)     
  {        
     return x > y;   
     // x小的優先級高       
     //也可以寫成其他方式,如: return p[x] > p[y];表示p[i]小的優先級高
  }
};
priority_queue<int, vector<int>, cmp> q; 
//定義方法
//其中,第一個爲容器內容物,第二個參數爲隊列使用的容器類型。第三個參數爲比較結構體。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章