【贪心算法】LeetCode2071:你可以安排的最多任务数目

简介: 【贪心算法】LeetCode2071:你可以安排的最多任务数目

题目

给你 n 个任务和 m 个工人。每个任务需要一定的力量值才能完成,需要的力量值保存在下标从 0 开始的整数数组 tasks 中,第 i 个任务需要 tasks[i] 的力量才能完成。每个工人的力量值保存在下标从 0 开始的整数数组 workers 中,第 j 个工人的力量值为 workers[j] 。每个工人只能完成 一个 任务,且力量值需要 大于等于 该任务的力量要求值(即 workers[j] >= tasks[i] )。

除此以外,你还有 pills 个神奇药丸,可以给 一个工人的力量值 增加 strength 。你可以决定给哪些工人使用药丸,但每个工人 最多 只能使用 一片 药丸。

给你下标从 0 开始的整数数组tasks 和 workers 以及两个整数 pills 和 strength ,请你返回 最多 有多少个任务可以被完成。

示例 1:

输入:tasks = [3,2,1], workers = [0,3,3], pills = 1, strength = 1

输出:3

解释:

我们可以按照如下方案安排药丸:

  • 给 0 号工人药丸。
  • 0 号工人完成任务 2(0 + 1 >= 1)
  • 1 号工人完成任务 1(3 >= 2)
  • 2 号工人完成任务 0(3 >= 3)
    示例 2:
    输入:tasks = [5,4], workers = [0,0,0], pills = 1, strength = 5
    输出:1
    解释:
    我们可以按照如下方案安排药丸:
  • 给 0 号工人药丸。
  • 0 号工人完成任务 0(0 + 5 >= 5)
    示例 3:
    输入:tasks = [10,15,30], workers = [0,10,10,10,10], pills = 3, strength = 10
    输出:2
    解释:
    我们可以按照如下方案安排药丸:
  • 给 0 号和 1 号工人药丸。
  • 0 号工人完成任务 0(0 + 10 >= 10)
  • 1 号工人完成任务 1(10 + 10 >= 15)
    示例 4:
    输入:tasks = [5,9,8,5,9], workers = [1,6,4,2,6], pills = 1, strength = 5
    输出:3
    解释:
    我们可以按照如下方案安排药丸:
  • 给 2 号工人药丸。
  • 1 号工人完成任务 0(6 >= 5)
  • 2 号工人完成任务 2(4 + 5 >= 8)
  • 4 号工人完成任务 3(6 >= 5)
    参数范围
    n == tasks.length
    m == workers.length
    1 <= n, m <= 5 * 104
    0 <= pills <= m
    0 <= tasks[i], workers[j], strength <= 109

分析

时间复杂度

O(lognnlogn)。二分查找可以完成的任务数,时间复杂度O(logn);枚举任务,时间复杂度O(n);每个任务二分查找,时间复杂度O(logn)。

二分查找

0个任务一定可以完成,随着任务数增加,变得不可完成。寻找最后一个可以完成任务的doNum,左闭右开空间。

完成doNum个任务

最强的doNum个工人是否能完成最简单的doNum个任务。从困难到容易枚举任务,如果有工人能完成,则直接完成。否则,吃药丸完成。无论是否吃药丸,在能完成任务的情况下,派最弱的人。

为什么能不吃药丸,就不吃药丸

假定任务i,b吃药丸才能完成,c可直接完成。

方案一:c直接完成。

方案二:b吃药丸完成。

除掉相同的工人和药丸。

方案一:b和一片药丸。

方案二:c。

如果方案二,能完成任务,则方案一也能完成任务。b吃药后,能完成余下任意任务。任务是从难到容易的。

注意:反之不成立。因为药丸可以给b以外的工人吃。

代码

核心代码

class Solution {
public:
int maxTaskAssign(vector& tasks, vector& workers, const int pills, const int strength) {
auto Can = [&](const int doNum)
{
int iNeedPill = 0;
std::multiset setWork(workers.rbegin() , workers.rbegin()+doNum);
for (int i = doNum - 1; i >= 0; i–)
{
auto it = setWork.lower_bound(tasks[i]);
if (setWork.end() != it)
{
setWork.erase(it);
continue;
}
if (iNeedPill >= pills)
{//没药丸了
return false;
}
it = setWork.lower_bound(tasks[i] - strength);
if (setWork.end() == it)
{
return false;//吃了药丸也无法完成任务
}
setWork.erase(it);
iNeedPill++;
}
return true;
};
sort(tasks.begin(), tasks.end());
sort(workers.begin(), workers.end());
int n = min(tasks.size(), workers.size());
int left = 0, right = n + 1;//左闭右开
while (right - left > 1)
{
const auto mid = left + (right - left) / 2;
if (Can(mid))
{
left = mid;
}
else
{
right = mid;
}
}
return left;
}
};

测试用例

template
void Assert(const vector& v1, const vector& v2)
{
if (v1.size() != v2.size())
{
assert(false);
return;
}
for (int i = 0; i < v1.size(); i++)
{
assert(v1[i] == v2[i]);
}
}
template
void Assert(const T& t1, const T& t2)
{
assert(t1 == t2);
}
int main()
{
vector tasks, workers;
int pills, strength, res;
{
Solution slu;
tasks = { 3, 2, 1 }, workers = { 0, 3, 3 }, pills = 1, strength = 1;
auto res = slu.maxTaskAssign(tasks, workers, pills, strength);
Assert(3, res);
}
{
Solution slu;
tasks = { 5, 4 }, workers = { 0, 0, 0 }, pills = 1, strength = 5;
auto res = slu.maxTaskAssign(tasks, workers, pills, strength);
Assert(1, res);
}
{
Solution slu;
tasks = { 10, 15, 30 }, workers = { 0, 10, 10, 10, 10 }, pills = 3, strength = 10;
auto res = slu.maxTaskAssign(tasks, workers, pills, strength);
Assert(2, res);
}
{
Solution slu;
tasks = { 5, 9, 8, 5, 9 }, workers = { 1, 6, 4, 2, 6 }, pills = 1, strength = 5;
auto res = slu.maxTaskAssign(tasks, workers, pills, strength);
Assert(3, res);
}
//CConsole::Out(res);

}

2023年3月版

class Solution {
public:
int maxTaskAssign(vector& tasks, vector& workers, int pills, int strength) {
const int iNum = min(tasks.size(), workers.size());
std::sort(tasks.begin(), tasks.end());
std::sort(workers.begin(), workers.end());
return Rev(0, iNum + 1, tasks, workers, pills, strength); }
int Rev(int iMin, int iMax, const vector& tasks, const vector& workers, int pills, int strength)
{
if (iMin + 1 == iMax)
{
return iMin;
}
const int iMid = (iMin + iMax) / 2;
if (Can(iMid, tasks, workers, pills, strength))
{
return Rev(iMid, iMax, tasks, workers, pills, strength);
}
return Rev(0, iMid, tasks, workers, pills, strength);
}
bool Can(int iFinishNum, const vector& tasks, const vector& workers, int pills, int strength)
{
std::multiset setWorks(workers.begin() + workers.size() - iFinishNum, workers.end());
for (int i = iFinishNum - 1; i >= 0;i–)
{
if (tasks[i] <= *setWorks.rbegin())
{
setWorks.erase(std::prev(setWorks.end()));
continue;
}
if (pills <= 0)
{
return false;
}
auto it = setWorks.lower_bound(tasks[i] - strength);
if (setWorks.end() == it )
{
return false;
}
pills–;
setWorks.erase(it);
}
return true;
}
};

优化

如果有工人,能直接完成。则选择任意一个能完成的工人,不必选择最弱的工人。因为任务是越来越容易,能完成当前任务,则能完成之后的任意任务。双向队列que记录所有吃药丸后能完成当前任务的工人,升序排列。先判断队尾能否直接完成,否则判断队首能否吃药丸完成。

代码

class Solution {
public:
  int maxTaskAssign(vector<int>& tasks, vector<int>& workers, const int pills, const int strength) {
    auto Can = [&](const int doNum)
    {
      int iNeedPill = 0;
      std::vector<int> vWork(workers.begin()+workers.size()- doNum , workers.end());//派出的工人
      std::deque<int> que;
      for (int i = doNum - 1, j = i; i >= 0; i--)
      {   
        while ((j>=0)&&(vWork[j]+strength >= tasks[i]))
        {   
          que.emplace_front(vWork[j--]);
        }
        if (que.empty())
        {
          return false;//没有共人能完成任务
        }
        if (que.back() >= tasks[i])
        {
          que.pop_back();
          continue;
        }
        if (iNeedPill >= pills)
        {//没药丸了
          return false;
        }       
        que.pop_front();
        iNeedPill++;
      }
      return true;
    };
    sort(tasks.begin(), tasks.end());
    sort(workers.begin(), workers.end());
    int n = min(tasks.size(), workers.size());
    int left = 0, right = n + 1;//左闭右开
    while (right - left > 1)
    {
      const auto mid = left + (right - left) / 2;
      if (Can(mid))
      {
        left = mid;
      }
      else
      {
        right = mid;
      }
    }
    return left;
  }
};


扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。

https://edu.csdn.net/course/detail/38771

如何你想快

速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程

https://edu.csdn.net/lecturer/6176

相关下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版

https://download.csdn.net/download/he_zhidan/88348653

。也就是我们常说的专业的人做专业的事。 |

|如果程序是一条龙,那算法就是他的是睛|

测试环境

操作系统:win7 开发环境: VS2019 C++17

或者 操作系统:win10 开发环境:

VS2022 C++17



相关文章
|
2月前
|
机器学习/深度学习 传感器 算法
【无人车路径跟踪】基于神经网络的数据驱动迭代学习控制(ILC)算法,用于具有未知模型和重复任务的非线性单输入单输出(SISO)离散时间系统的无人车的路径跟踪(Matlab代码实现)
【无人车路径跟踪】基于神经网络的数据驱动迭代学习控制(ILC)算法,用于具有未知模型和重复任务的非线性单输入单输出(SISO)离散时间系统的无人车的路径跟踪(Matlab代码实现)
193 2
|
1月前
|
存储 人工智能 算法
从零掌握贪心算法Java版:LeetCode 10题实战解析(上)
在算法世界里,有一种思想如同生活中的"见好就收"——每次做出当前看来最优的选择,寄希望于通过局部最优达成全局最优。这种思想就是贪心算法,它以其简洁高效的特点,成为解决最优问题的利器。今天我们就来系统学习贪心算法的核心思想,并通过10道LeetCode经典题目实战演练,带你掌握这种"步步为营"的解题思维。
|
8月前
|
算法
一次推理,实现六大3D点云分割任务!华科发布大一统算法UniSeg3D,性能新SOTA
华中科技大学研究团队提出了一种名为UniSeg3D的创新算法,该算法通过一次推理即可完成六大3D点云分割任务(全景、语义、实例、交互式、指代和开放词汇分割),并基于Transformer架构实现任务间知识共享与互惠。实验表明,UniSeg3D在多个基准数据集上超越现有SOTA方法,为3D场景理解提供了全新统一框架。然而,模型较大可能限制实际部署。
634 15
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
137 0
|
存储 算法 Java
leetcode算法题-有效的括号(简单)
【11月更文挑战第5天】本文介绍了 LeetCode 上“有效的括号”这道题的解法。题目要求判断一个只包含括号字符的字符串是否有效。有效字符串需满足左括号必须用相同类型的右括号闭合,并且左括号必须以正确的顺序闭合。解题思路是使用栈数据结构,遍历字符串时将左括号压入栈中,遇到右括号时检查栈顶元素是否匹配。最后根据栈是否为空来判断字符串中的括号是否有效。示例代码包括 Python 和 Java 版本。
278 4
|
算法
每日一道算法题(Leetcode 20)
每日一道算法题(Leetcode 20)
160 2
|
算法 Java
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
174 6
|
人工智能 算法 Java
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
183 1
|
存储 算法 Java
LeetCode经典算法题:预测赢家+香槟塔java解法
LeetCode经典算法题:预测赢家+香槟塔java解法
196 1
|
存储 算法 Java
LeetCode经典算法题:二叉树遍历(递归遍历+迭代遍历+层序遍历)以及线索二叉树java详解
LeetCode经典算法题:二叉树遍历(递归遍历+迭代遍历+层序遍历)以及线索二叉树java详解
220 0

热门文章

最新文章