安排工作以达到最大收益 图解题解
这道题到底在问什么
- 输入
- difficulty=[2,4,6,8,10], profit=[10,20,30,40,50], worker=[4,5,6,7]
- 输出
- 100
最优解:一步一步想明白
- 3记住「按难度排序工作、按能力排序工人;指针不回退地纳入难度够到的工作、维护最大收益 best,每个工人取 best」,下面每帧都在套它。
- 4先排序。工作按难度排好:难度 [2, 4, 6, 8, 10]、对应收益 [10, 20, 30, 40, 50](右边面板就是这张「难度→收益」对照表);工人按能力排好 [4, 5, 6, 7]。排序是双指针不回退的前提。
- 5来了个能力 4 的工人。他能做难度不超过 4 的任意工作,目标是从这些工作里挑收益最高的。指针从上一个工人停下的位置接着往右扫,不回退。
- 6难度 2 不超过 4,这份工作他做得了,纳入候选。它的收益是 10,比之前的 best 高,best 更新成 10。注意 best 只增不减,后面工人能力更强、能用的工作只多不少。
- 7难度 4 不超过 4,这份工作他做得了,纳入候选。它的收益是 20,比之前的 best 高,best 更新成 20。注意 best 只增不减,后面工人能力更强、能用的工作只多不少。
- 8再往右,难度 6 已经大于 4,这个工人做不了,停。指针就停在这里,等下一个能力更强的工人接着往右走。
- 9这个工人就拿他能力内的最高收益 20,加进总收益,现在 ans = 20。他不一定做难度最高那份,而是做收益最高那份,这正是要维护 best 的原因。
- 10来了个能力 5 的工人。他能做难度不超过 5 的任意工作,目标是从这些工作里挑收益最高的。指针从上一个工人停下的位置接着往右扫,不回退。
- 11再往右,难度 6 已经大于 5,这个工人做不了,停。指针就停在这里,等下一个能力更强的工人接着往右走。
- 12这个工人就拿他能力内的最高收益 20,加进总收益,现在 ans = 40。他不一定做难度最高那份,而是做收益最高那份,这正是要维护 best 的原因。
- 13来了个能力 6 的工人。他能做难度不超过 6 的任意工作,目标是从这些工作里挑收益最高的。指针从上一个工人停下的位置接着往右扫,不回退。
- 14难度 6 不超过 6,这份工作他做得了,纳入候选。它的收益是 30,比之前的 best 高,best 更新成 30。注意 best 只增不减,后面工人能力更强、能用的工作只多不少。
- 15再往右,难度 8 已经大于 6,这个工人做不了,停。指针就停在这里,等下一个能力更强的工人接着往右走。
- 16这个工人就拿他能力内的最高收益 30,加进总收益,现在 ans = 70。他不一定做难度最高那份,而是做收益最高那份,这正是要维护 best 的原因。
- 17来了个能力 7 的工人。他能做难度不超过 7 的任意工作,目标是从这些工作里挑收益最高的。指针从上一个工人停下的位置接着往右扫,不回退。
- 18再往右,难度 8 已经大于 7,这个工人做不了,停。指针就停在这里,等下一个能力更强的工人接着往右走。
- 19这个工人就拿他能力内的最高收益 30,加进总收益,现在 ans = 100。他不一定做难度最高那份,而是做收益最高那份,这正是要维护 best 的原因。
⚠️ 容易写错的地方
✗ 错:工人取「当前这份工作」的收益
✓ 对:取「能力内见过的最高收益」best
难度高收益不一定高,best 要在纳入时维护最大值,不能只看最后一份
✗ 错:每个工人都从头扫工作
✓ 对:工人升序后指针 i 不回退
能力只增不减,前面纳入的工作后面还能用,从头扫白白多花时间
✗ 错:忘了 best 单调
✓ 对:best 只增不减、跨工人保留
能力更强的工人可选范围更大,沿用之前的 best 再纳入新工作即可
完整代码(Python / C++ / Java)
Python
from typing import List
class Solution:
def maxProfitAssignment(self, difficulty: List[int], profit: List[int], worker: List[int]) -> int:
jobs = sorted(zip(difficulty, profit))
worker.sort()
ans = best = i = 0
for w in worker:
while i < len(jobs) and jobs[i][0] <= w:
best = max(best, jobs[i][1])
i += 1
ans += best
return ansC++
#include <algorithm>
#include <vector>
using namespace std;
class Solution {
public:
int maxProfitAssignment(vector<int>& difficulty, vector<int>& profit, vector<int>& worker) {
vector<pair<int,int>> jobs;
for (int i = 0; i < (int)difficulty.size(); ++i) jobs.push_back({difficulty[i], profit[i]});
sort(jobs.begin(), jobs.end());
sort(worker.begin(), worker.end());
int ans = 0, best = 0, i = 0;
for (int w : worker) {
while (i < (int)jobs.size() && jobs[i].first <= w) best = max(best, jobs[i++].second);
ans += best;
}
return ans;
}
};Java
import java.util.*;
class Solution {
public int maxProfitAssignment(int[] difficulty, int[] profit, int[] worker) {
int n = difficulty.length;
int[][] jobs = new int[n][2];
for (int i = 0; i < n; i++) { jobs[i][0] = difficulty[i]; jobs[i][1] = profit[i]; }
Arrays.sort(jobs, Comparator.comparingInt(a -> a[0]));
Arrays.sort(worker);
int ans = 0, best = 0, i = 0;
for (int w : worker) {
while (i < n && jobs[i][0] <= w) best = Math.max(best, jobs[i++][1]);
ans += best;
}
return ans;
}
}复杂度
时间
O(n log n + m log m)
设工作数 n、工人数 m:排序工作 O(n log n) + 排序工人 O(m log m) 是瓶颈;之后指针 i 不回退、每份工作只扫一次,工人各 O(1) 取 best,这部分 O(n+m) 线性
空间
O(n)
打包(难度,收益)对需要 O(n);排序栈空间另算
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 安排工作以达到最大收益 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
不排序能做吗?+
能用别的思路:比如把能力上限内的最大收益预处理成「难度→该难度及以下最大收益」的前缀最大值数组,再对每个工人二分能力位置查最大收益,总复杂度 O(n log n + m log n)(n 工作数、m 工人数;n、m 同阶时可视作 O(N log N))。但排序 + 双指针写起来最简洁、常数更小。
和「分发饼干」这类贪心有什么共性?+
都是「两个序列各自排序后用双指针贪心匹配」。区别在匹配规则:分发饼干是一一满足,本题是每个工人独立取能力内最优,并且靠 best 单调把扫描压成线性。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 安排工作以达到最大收益 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。