优先队列与双指针:最小化雇佣成本的算法解析

news2024/10/8 22:56:45

问题描述

给定一个下标从 0 开始的整数数组 costs,其中 costs[i] 是雇佣第 i 位工人的代价。还给定两个整数 kcandidates,我们需要按照以下规则来雇佣恰好 k 位工人:

  • 总共进行 k 轮雇佣,每一轮雇佣一位工人。
  • 在每一轮雇佣中,从最前面的 candidates最后面的 candidates中选出代价最小的工人。如果有多个代价相同的工人,选择下标较小的工人。
  • 每一位工人只能被雇佣一次。

我们的目标是以最小的总代价雇佣到 k 位工人。

题目链接:2462. 雇佣 K 位工人的总代价 - 力扣(LeetCode)

解题思路

该问题可以通过 优先队列(即堆)来高效地解决。利用优先队列,我们能够快速找到当前候选工人中代价最小的工人。核心思路如下:

  1. 使用两个优先队列 分别管理前 candidates 和后 candidates 工人,以方便比较和选择代价最小的工人。
  2. 每一轮选择时,比较两个队列中代价最小的工人,选择代价更低的工人进行雇佣。
  3. 双指针扩展范围,每次从工人列表的前后两端向内缩进,保证总是有足够的候选工人供选择。

关键步骤与优化

  1. 双端优先队列
    通过两个最小堆分别管理前 candidates 个工人和后 candidates 个工人。这样可以保证我们能够在常数时间内找到代价最小的工人。

  2. 双指针
    我们使用两个指针 leftright,分别从工人列表的两端向内移动,用于动态管理当前可以选择的工人范围。

  3. 选择与更新
    每轮选择时,比较两个堆的堆顶工人(代价最小的工人),然后将该工人移出堆,并从剩余的工人中补充新工人进入对应的堆中。

代码实现

我们通过以下步骤逐步编写代码:

  1. 初始化两个优先队列(小顶堆)和双指针。
  2. 在每轮中从两个队列中选出代价最小的工人。
  3. 更新代价总和,并将已选择的工人从候选人中移除,继续补充新工人进入队列。
#include <iostream>
#include <vector>
#include <queue>
#include <functional>

using namespace std;

long long hireWorkers(vector<int>& costs, int k, int candidates) {
    // 定义两个小顶堆,分别管理前面的 candidates 个工人和后面的 candidates 个工人
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> leftHeap, rightHeap;
    long long cost = 0;  // 用于存储最终的总代价
    int left = 0, right = costs.size() - 1;

    // 初始化前面部分的候选工人
    while (left < candidates && left <= right) {
        leftHeap.push({costs[left], left});
        left++;
    }
    // 初始化后面部分的候选工人
    while (right >= costs.size() - candidates && left <= right) {
        rightHeap.push({costs[right], right});
        right--;
    }

    // 每轮选择一个工人,进行 k 轮选择
    while (k > 0) {
        k--;

        // 比较两个堆的堆顶,选择代价较小的工人
        if (!leftHeap.empty() && (rightHeap.empty() || leftHeap.top().first <= rightHeap.top().first)) {
            cost += leftHeap.top().first;  // 累加最小工人的代价
            int idx = leftHeap.top().second;
            leftHeap.pop();  // 从堆中移除该工人

            // 如果还有工人未被考虑,将新工人加入左堆
            if (left <= right) {
                leftHeap.push({costs[left], left});
                left++;
            }
        } else {
            cost += rightHeap.top().first;  // 累加最小工人的代价
            int idx = rightHeap.top().second;
            rightHeap.pop();  // 从堆中移除该工人

            // 如果还有工人未被考虑,将新工人加入右堆
            if (left <= right) {
                rightHeap.push({costs[right], right});
                right--;
            }
        }
    }

    return cost;  // 返回雇佣 k 位工人的总代价
}

int main() {
    vector<int> costs1 = {17, 12, 10, 2, 7, 2, 11, 20, 8};
    int k1 = 3;
    int candidates1 = 4;
    cout << "Total hiring cost: " << hireWorkers(costs1, k1, candidates1) << endl;

    vector<int> costs2 = {1, 2, 4, 1};
    int k2 = 3;
    int candidates2 = 3;
    cout << "Total hiring cost: " << hireWorkers(costs2, k2, candidates2) << endl;

    return 0;
}

代码详细解释

  1. 堆和指针初始化

    • leftHeaprightHeap 分别管理前 candidates 和后 candidates 个工人。
    • leftright 是双指针,分别用于记录左边和右边尚未加入堆的工人位置。
  2. 每轮工人选择

    • k 控制循环,每次从两端候选堆中选择代价最小的工人。
    • 比较 leftHeaprightHeap 的堆顶,选择代价更低的一侧。
    • 根据选择的堆,将该工人移出并从剩余候选工人中补充新工人进入堆中。
  3. 终止条件

    • 使用 k 控制循环,每次选择一位工人。
    • 比较 leftHeaprightHeap 的堆顶(最小元素),选择代价最小的工人。
    • 从对应的堆中删除该工人,并根据当前剩余的工人情况从剩余部分补充新的工人进入堆中。
    • 注意:
      • 如果左边堆的堆顶较小或者右边堆为空,选择左边的工人,并且从剩余部分添加一个新的工人进入左边堆。
      • 如果右边堆较小或者左边堆为空,则选择右边的工人并从剩余部分添加一个新的工人进入右边堆。

关键逻辑解析

在代码中,以下这段判断逻辑是关键所在:

if (!leftHeap.empty() && (rightHeap.empty() || leftHeap.top().first <= rightHeap.top().first)) {

这一行代码决定了每一轮应该选择哪个堆中的工人,并且需要处理多个边界情况。我们来详细解释其中的逻辑:

  1. !leftHeap.empty()

    • 首先确保左边的优先队列 leftHeap 不为空。这是为了防止从空堆中取元素。
  2. rightHeap.empty()

    • 如果 rightHeap 为空,意味着右边部分没有工人可供选择,因此必须从 leftHeap 中选择。
  3. leftHeap.top().first <= rightHeap.top().first

    • 如果 rightHeap 不为空,则需要比较两个堆的堆顶工人代价,选择代价更小的那个。
    • 使用 <= 是为了保证当两个堆顶的代价相等时,我们优先选择左边的工人,因为题目要求在代价相同的情况下选择下标较小的工人。

if 条件确保我们总是从两个部分中选择代价更小的工人,并且当代价相等时,优先选择下标更小的那一侧。通过这种方式,我们能够保证每一轮雇佣的工人都是代价最小且满足题目要求的。

else 条件的分析

如果不满足上述 if 条件,那么会执行 else 分支,即从 rightHeap 中选择工人。这种情况会发生在以下几种场景:

  1. leftHeap 为空

    • 如果 leftHeap 已经空了,而 rightHeap 仍有可供选择的工人,那么我们只能从右边部分中选择。
  2. rightHeap 的代价更小

    • 当 rightHeap 中的堆顶工人的代价比 leftHeap 中的更小时,我们自然选择代价更小的工人来满足代价最小化的需求。

通过这种逻辑,我们确保每一轮总是优先从两端候选人中选择代价最低的工人,并且当左边堆为空时合理地从右边选择。这种方法保证了问题中的所有条件得到了满足,并且最小化了雇佣的总代价。

示例分析

示例 1:

输入:costs = [17, 12, 10, 2, 7, 2, 11, 20, 8], k = 3, candidates = 4

  • 第一轮
    • leftHeap 初始化为 [17, 12, 10, 2]rightHeap 初始化为 [7, 2, 11, 20]
    • 选择代价最小的工人:从 leftHeap 选择 2,总代价为 2
  • 第二轮
    • leftHeap 更新为

[17, 12, 10, 7]rightHeap 更新为 [7, 11, 20]

  • 选择代价最小的工人:从 rightHeap 选择 2,总代价为 4
  • 第三轮
    • leftHeap 更新为 [17, 12, 10]rightHeap 更新为 [7, 11]
    • 选择代价最小的工人:从 rightHeap 选择 7,最终总代价为 11

输出:11

总结

通过这个题目,我们学到了如何使用优先队列(小顶堆)来动态地管理双端队列中的候选人,并以此来确保每次选择的工人代价最低。我们使用双指针和两个优先队列分别管理前后部分,从而高效地实现了对 costs 数组的动态管理。

重点逻辑在于合理比较两个堆的堆顶,确保代价最小且符合题目的要求。希望通过这篇博客,你可以更好地理解双端优先队列的使用以及复杂条件判断的技巧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2197404.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LSTM变种模型

一、GRU 1.概念 GRU&#xff08;门控循环单元&#xff0c;Gated Recurrent Unit&#xff09;是一种循环神经网络&#xff08;RNN&#xff09;的变体&#xff0c;旨在解决标准 RNN 在处理长期依赖关系时遇到的梯度消失问题。GRU 通过引入门控机制简化了 LSTM&#xff08;长短期…

Python爬虫使用实例-jsyks

目标地址&#xff1a; https://www.jsyks.com/kmy-mnks例如&#xff1a; urlhttps://www.jsyks.com/kmy-mnks # kmy-mnks 科目一-模拟考试 urlhttps://www.jsyks.com/kms-mnks # kms-mnks 科目四-模拟考试一、获取资源 先从本题分析里面得到解析答案【通过div.Exam ul li里面…

面向对象技术——设计模式

目录 层次结构 具体设计模式分类 创建型模式&#xff08;处理创建对象&#xff09; 结构型模式&#xff08;处理类和对象的组合&#xff09; 行为型模式&#xff08;描述类或者对象的交互行为&#xff09; 创建型设计模式 ​编辑 结构型设计模式 行为型设计模式​编辑 …

时序论文17|ICML24 SAMformer:华为新奇视角讨论Transformer时序预测时的收敛优化问题

论文标题&#xff1a;SAMformer: Unlocking the Potential of Transformers in Time Series Forecasting with Sharpness-Aware Minimization and Channel-Wise Attention 论文链接&#xff1a;https://arxiv.org/abs/2402.10198 代码链接&#xff1a;https://github.com/rom…

从零开始:在 VMware ESXi 环境中安装 Rocky Linux 的秘诀

哈喽大家好&#xff0c;欢迎来到虚拟化时代君&#xff08;XNHCYL&#xff09;。 “ 大家好&#xff0c;我是虚拟化时代君&#xff0c;一位潜心于互联网的技术宅男。这里每天为你分享各种你感兴趣的技术、教程、软件、资源、福利…&#xff08;每天更新不间断&#xff0c;福利…

appium中的uiautomatorviewer显示的界面为横屏解决方法

uiautomatorviewer显示的界面为横屏解决方法 解决方法&#xff1a; 修改模拟器的分辨率&#xff0c;比如540:900就可解决了

MySQL基础教程(一):连接数据库和使用表

这个专栏用来讲解 MySQL 数据的基本语法和用法。本教程的目的是方便查询 MySQL 的用法&#xff0c;因此对于原理的讲解会偏少一些&#xff0c;但特点就是会有很多实验操作图。 本专栏使用的源材料是《MySQL必知必会》这本书的源代码。 文章目录 1. 连接 MySQL 数据库2. 创建数…

搭建个人博客--1、前端页面

采用bootstrap前端框架&#xff1a; Anchor - a free Bootstrap UI Kit综合使用bootstrap框架做一个Aotm Blog个人博客_基于bootstrap的博客-CSDN博客 做出模板base.html {% load static %} <!DOCTYPE html> <html langen> <head><meta charset"UT…

Facebook账单户和海外户该如何选择?

近期&#xff0c;有不少小伙伴来咨询广告投放账户的问题&#xff0c;很多人把账单户作为广告投放的选择&#xff0c;那么账单户和海外户哪个更适合你呢&#xff1f;本文将详细探讨这两种账户类型的差异&#xff0c;以及在不同情境下应如何选择&#xff0c;感兴趣的朋友就继续看…

遨游双卫星智能终端,赋能石油行业安全生产和信息化建设

石油&#xff0c;被誉为“工业的血液”&#xff0c;其影响力深远&#xff0c;石油勘探与开发活动往往在人迹罕至的偏远区域展开&#xff0c;如广袤的戈壁滩、浩瀚的海洋&#xff0c;这些区域普遍缺乏健全的公共电信网络基础设施。以往&#xff0c;油田野外作业团队主要依赖短波…

避雷!Google Adsense联盟营销七大投放误区

你是否在使用Google AdSense进行广告投放&#xff1f;你是否想进一步优化你的投放策略&#xff1f;那么这篇文章你不可错过啦&#xff01; Google AdSense为跨境商家提供了一个平台&#xff0c;我们可以通过展示相关广告来赚取收入。然而&#xff0c;即使是最有经验的商家也可…

API项目:模拟接口开发和调用

创建模拟接口 controller 层&#xff1a; 控制层&#xff0c;负责处理用户请求&#xff0c;并根据请求调用相应的业务逻辑&#xff0c;然后返回对应的视图或数据。 model 层&#xff1a; 数据模型层&#xff0c;负责数据的处理和业务逻辑&#xff1b;在 model 层中&#xf…

无人机在矿业领域的应用!

矿区测绘与建模 无人机可以快速、全面地获取矿区的地形地貌数据&#xff0c;生成高精度的二维或三维模型。 这些模型可用于矿区的规划、设计、监测和管理&#xff0c;提高矿山的生产效率。 库存量量化监测 无人机能够捕捉厘米级的地形数据&#xff0c;通过计算得出准确的库…

ADC -模数转换

ADC -模数转换 - 将模拟信号转换为数字信号 目录 ADC -模数转换 - 将模拟信号转换为数字信号 STM32方面使用的AD转化方式是逐次逼近法 ADC 什么叫单次&#xff0c;连续&#xff0c;扫描&#xff0c;中断&#xff1f; 应用&#xff1a;运用STM32中ADC转…

Vue入门-小黑课堂Demo

功能需求&#xff1a; ①列表渲染 ②删除功能 ③添加功能 ④底部统计和清空 页面效果&#xff1a; 代码展示&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" c…

年会工作会议会务报名签到小程序开源版开发

年会工作会议会务报名签到小程序开源版开发 会议管理微信小程序&#xff0c;对会议流程、开支、数量、标准、供应商提供一种标准化的管理方法。以达到量化成本节约&#xff0c;风险缓解和服务质量提升的目的。适用于大型论坛、峰会、学术会议、政府大会、合作伙伴大会、经销商…

大多数人不知道的:线程池CallerRunsPolicy()拒绝策略

总所周知&#xff0c;java里面线程池的四个拒绝策略 AbortPolicy 丢弃并抛出RejectedExecutionException异常 DiscardPolicy 直接丢弃 DiscardOldestPolicy 直接丢弃最前面的任务&#xff0c;尝试执行新任务 CallerRunsPolicy 由调用线程池的线程处理任务&a…

linux安装minianconda

文章目录 &#x1f315;我的配置&#x1f315;从清华镜像源里下载minianaconda&#x1f315;安装&#x1f315;自定义安装位置&#x1f315;是否关闭打开终端默认进入anaconda的设置&#xff1f;&#x1f315;配置清华镜像源 &#x1f315;我的配置 ubuntu 22.04LTS &#x1…

全都燃起来了!黄金周车市销量成绩出炉

文/王俣祺 导语&#xff1a;国庆黄金周对于其他行业可能是个放松的好时机&#xff0c;但对于国内汽车市场而言可能是下半年最关键的“战场”。这几天&#xff0c;全国各地的车展和4S店简直热闹非凡&#xff0c;新能源车尤其抢镜&#xff0c;优惠活动不断&#xff0c;引得消费者…

DAMA数据管理知识体系(第9章 文件和内容管理)

课本内容 9.1 引言 概要 文件和内容管理是指针对存储在关系型数据库之外的数据和信息的采集、存储、访问和使用过程的管理[1]。它的重点在于保持文件和其他非结构化或半结构化信息的完整性&#xff0c;并使这些信息能够被访问。业务驱动因素 法规遵从性要求 法律法规要求组织保…