LeetCode 630 课程表 3

346 阅读2分钟
/**
 * @param {number[][]} courses
 * @return {number}
 * 这道题典型的用到了高中数学里的递推
 * 关键:
 * 1.如果两门课程[t1, d1], [t2, d2]有 d1 <= d2, 那么先学前者是最优的, 因为
 * 如果存在两门课程都学的可能性的话, 先学前者就一定能接着学后者、但是先学后者不一定能再学前者。
 * 起始对于多门课程也是这样, 优先学习d小的课程才可能多学,证明是类似的.
 * 这就是必须排序的原因
 * 2.先定义最优选择: n门课中选择的课程数k最大、k门课总耗时最短称为最优选择。
 * 如果前i门课中最优选择是t1、t2...tk, 那么假设最优课程数是k +1,应该怎么处理:
 * (1) 如果 t1 + t2 + ... + tk + t0 <= d0, 那么应该直接把t0加进来, 最优解变成了k + 1门课,
 * 这个可用反证法证明;
 * (2)如果t1 + t2 + ... + tk + t0 > d0, 那么应该把t1、t2...tk中耗时最多的一门课去掉, 再加入t0,这时最优解是k门课。同样可用反证法证明
 * (3)
 */
// 排序的作用其实是找到结束时间最小的那门课
// 因为递推需要一个起始的最优解
var scheduleCourse = function(courses) {
    courses.sort((a, b) => a[1] - b[1])
    let total = 0
    let ans = 0
    const queue = new Queue()
    for(let x of courses) {
        if((total +x[0]) <= x[1]) {
            total += x[0]
            ans++
            queue.add(x)
        } else{
            let top = queue.peek()
            if(top && (top[0] > x[0])) {
                queue.pop()
                total = total - top[0] + x[0]
                queue.add(x)
            }
        }
    }
    return ans
};

function Queue() {
    this.list = []
}

// 自定义优先队列, 升序排
Queue.prototype.add = function(ele) {
    let l = 0
    let r = this.list.length
    while(l < r) {
        const m = ((l + r) / 2) | 0
        if(ele[0] > this.list[m][0]) {
            l = m + 1
        } else if(ele[0] === this.list[m][0]) {
            l = m
            break
        } else r = m
    }
    this.list.splice(l, 0, ele)
}
// 弹出队头元素
Queue.prototype.pop = function() {
    this.list.pop()
}

Queue.prototype.peek = function() {
    return this.list.length ? this.list[this.list.length - 1] : null
}