前端就该用 JS 刷算法31

118 阅读3分钟

每日一题 -- 堆

857. 雇佣 K 名工人的最低成本

857. 雇佣 K 名工人的最低成本

分析

  1. 首先根据 q/w 质量除以底薪,得到的就是每位员工的工作效率,也就是单位钱能干的活,可以以性价比来替代
  2. 我们都想用性价比高的员工,但是现在要求所有员工这个性价比必须一致,多劳多得,而这个性价比呢得至少满足其中一位员工的期望底薪
  3. 所以我们先将性价比排个序,性价比低的能,就是底薪高干活少,所以只要满足低性价比人,高性价比的人就只需要挑相对来说干活没那么强的人(以前性价比高,是要钱少,现在按比例涨价,这些性价比高的瞬间就成了废物,要多补很多钱;因为之前要的钱太少,现在补贴太多,公司都不愿意要,所以啊,做人不能太低估自己,不然政策也保护不了你;打工人就是苦啊;)
  4. 所以根据质量维护一个大顶堆,然后用排序好的性价比作为基数,每当超出了 K 个值,就从大顶堆中推出质量最高的,保证最后用钱最少。
  5. 其实这个问题和现实的倒挂现象很相似,新来的应届生效率没你好,但是时薪比你高,当你要拿一样的时薪的时候,公司就会干掉你,这说明有一些不能单纯用效率来体现性价比的东西影响了老板的判断,比方说,年龄???
// 857. 雇佣 K 名工人的最低成本
// https://leetcode-cn.com/problems/minimum-cost-to-hire-k-workers/


/**
 * @分析
 * 1. q/w, 工作效率,质量越高,钱越少当然是最好 -- 性价比高
 * 2. 性价比低的可以兼容性价比高的,因为性价比低的要钱多啊,只要效率低的都达到底薪,性价比高肯定能
 * 3. 所以这道题先用一个数组,按性价比从高到低排序,每次取性价比低的作为基准,然后根据在此基准上,取尽量少的质量,这样可以少花钱
 */
var mincostToHireWorkers = function(quality, wage, K) {
    let res = Infinity
    let total = 0 
    // 一个长度为 K的大顶堆,维护的值为质量
    const heap = new Heap()
    let rates = []
    for(let i = 0;i<quality.length;i++){
        rates.push([quality[i]/wage[i],quality[i]])
    }
    rates.sort((a,b)=>b[0]-a[0])
    for(let i = 0;i<rates.length;i++){
            // 每次获取到的 rate 都是当前堆里最小的
            const [rate,q] = rates[i]
            heap.heappush(-q)
            total += q
            if(heap.data[0] > K){
                // 多了,所以要删除一个
                total -= -heap.heappop()
            }
            console.log(heap,res,total,rate)
            if(heap.data[0] === K) {
                res = Math.min(res,total/rate)
            }
    }
    return res
   
};



/**
 * 实现小顶堆
 */

const Heap = function () {
    this.data = []
    // 第一个值是堆的实际长度,也就是堆末的下标
    this.data.push(0)
}


//  追加一个值
Heap.prototype.heappush = function (val) {
    // 往尾部加一个值,然后 up 上去
    this.data.push(val)
    this.data[0] += 1
    this.up(this.data[0])
}

//  弹出堆顶
Heap.prototype.heappop = function () {
    // 先将堆顶和堆最后一个值交换,删除,然后down 下来
    this.swap(1, this.data[0])
    const res = this.data.pop()
    this.data[0] -= 1
    this.down() // 默认就是从 1 开始
    return res // 将 pop 出来的值保存一下
}

Heap.prototype.swap = function (a, b) {
    [this.data[a], this.data[b]] = [this.data[b], this.data[a]]
}

// 这边第一个值就当是当前堆的长度好了 -- 默认是从根节点开始
Heap.prototype.down = function (index = 1) {
    // 已经到最后一个可以找到左右节点的第二层节点了,这样 left 和 right 就不需要再判断了
    if (index*2 > this.data[0]) return
    const left = 2 * index
    const right = 2 * index + 1
    let target = index
    if (left<=this.data[0] &&this.data[left] < this.data[target]) {
        target = left
    }
    if (right<=this.data[0] &&this.data[right] < this.data[target]) {
        target = right
    }
    if (target !== index) {
        this.swap(target, index)
        // 换的是值,还得继续往下面走
        this.down(target)
    }
    // 如果没变,证明走到这里已经整理完了,下面的子树已经是ok的了
}

Heap.prototype.up = function (index) {
    if (index < 2) return
    const fatherIndex = Math.floor(index / 2)
    // 只需要和父节点比较,兄弟节点是没有比较的价值的
    if (this.data[index] < this.data[fatherIndex]) {
        this.swap(index, fatherIndex)
        this.up(fatherIndex)
    }
}