贪心算法练习

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. 带记录的递归

  • 对于每一个课程,我们都有选(前提是不冲突)和不选两种可能
  • 一旦我们选择一门课程,我们当然是尽可能把它尽可能提前学完,因此,我们只需维护一个已选课程花费的时间和 t i m e time time,在判断是否冲突的时候,看当前课程所需时间和 t i m e time time相加是否超过 d d l ddl ddl即可
  • 为了实现这样的判断,我们需要将课程按结束时间从小到达排序
  • 在递归选择的过程中,用数组记录,节约开销
  • 时间复杂度和空间复杂度都是 O ( n ∗ d ) O(n*d) O(nd),在本题中显然容易超时

2.替换策略的贪心算法

  • 考虑一种贪心算法,只需要遍历一次所有课程(按结束时间排好序),如果一个课程与当前选择方案不冲突,加进去;如果冲突,则将其与当前方案的所有课程持续时间最长者比较,如果它持续时间短,替换之
  • 证明这种方法有效
    • 首先,由于没有设置课程起始时间,课程肯定是越早上完越好
    • 其次,用一个时间短的课程替换已选方案时间长的,肯定安全,而且肯定给后面的课程留下更多时间
    • 并且,替换者和被替换者肯定不相容,如果相容在前一步的判断中就加进去了
  • 设答案是 r e s res res,那么遍历查找的开销就是 O ( n ∗ c o u n t ) O(n*count) O(ncount),空间复杂度 O ( 1 ) O(1) O(1)

3.优先队列进行优化

  • 其实前一种思路我们可以进一步优化对当前方案中时间最长课程的查找
  • 对于c++中基于堆的priority_queue,时间复杂度是这样的
    • push() push(x) 令x入队,时间复杂度为 O ( l o g N ) O(log N) O(logN), 其中 N 为当前优先队列中元素的个数
    • top() 获得队首元素(即堆顶元素),时间复杂度为 O ( 1 ) O(1) O(1)
    • pop() 令队首元素(即堆顶元素)出队,时间复杂度为 O ( l o g N ) O(log N) O(logN)
    • empty() 检测优先队列是否为空,返回 true则空,返回false 则非空。时间复杂度为 O ( 1 ) O(1) O(1)
    • size() 返回优先队列内元素的个数,时间复杂度为$ O(1)$
  • 这样可以时总的时间复杂度降到 O ( n l o g ( n ) ) 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; 
//定义方法
//其中,第一个为容器内容物,第二个参数为队列使用的容器类型。第三个参数为比较结构体。
    原文作者:算法
    原文地址: https://www.twblogs.net/a/5bd3ac2f2b717778ac20ad62
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞