Hard
题目描述
给你一个任务数组 tasks,其中 tasks[i] = [actuali, minimumi]:
- actuali 是完成第 i 个任务 实际需要 的能量。
- minimumi 是开始第 i 个任务前需要的 最少 能量。
比如,如果任务是 [10, 12] 且你的当前能量是 11,那么你不能开始这个任务。如果你的当前能量是 13,你可以完成这个任务,任务完成后剩余能量为 3。
你可以按照 任意顺序 完成任务。
请你返回能够完成所有任务的 最少初始能量。
示例 1:
输入:tasks = [[1,2],[2,4],[4,8]]
输出:8
解释:
一开始有 8 能量,我们按照如下顺序完成任务:
- 第 3 个任务,完成后能量 = 8 - 4 = 4
- 第 2 个任务,完成后能量 = 4 - 2 = 2
- 第 1 个任务,完成后能量 = 2 - 1 = 1
注意到即使完成所有任务后还有剩余能量,我们无法在一开始只有 7 能量的情况下完成任务,因为我们无法完成第 3 个任务。
示例 2:
输入:tasks = [[1,3],[2,4],[10,11],[10,12],[8,9]]
输出:32
示例 3:
输入:tasks = [[1,7],[2,8],[3,9],[4,10],[5,11],[6,12]]
输出:27
提示:
- 1 <= tasks.length <= 10^5
- 1 <= actuali <= minimumi <= 10^4
解题思路
这道题的关键在于找到最优的任务执行顺序,然后计算所需的最少初始能量。
思路分析:
贪心策略:我们需要找到一个排序策略,使得按这个顺序执行任务时,所需的初始能量最少。关键观察是任务的执行顺序会影响所需的初始能量。
排序规则:对于两个任务 A[actual_a, min_a] 和 B[actual_b, min_b],我们应该优先执行哪一个?
- 如果先执行A再执行B,需要的最小能量是 max(min_a, min_b + actual_a)
- 如果先执行B再执行A,需要的最小能量是 max(min_b, min_a + actual_b)
- 比较这两个值,选择较小的执行顺序
- 化简后得到排序规则:按 (min_i - actual_i) 降序排列
计算初始能量:排序后从后往前计算,维护完成剩余任务所需的最小能量。对于每个任务,初始能量至少要满足:
- 能够开始当前任务:>= min_i
- 完成当前任务后能完成剩余任务:>= actual_i + 剩余任务所需能量
优化方法:也可以使用二分搜索,因为判断函数具有单调性。
推荐解法:贪心 + 排序,时间复杂度更优。
代码实现
class Solution {
public:
int minimumEffort(vector<vector<int>>& tasks) {
// 按照 minimum - actual 降序排列
sort(tasks.begin(), tasks.end(), [](const vector<int>& a, const vector<int>& b) {
return (a[1] - a[0]) > (b[1] - b[0]);
});
int totalActual = 0;
for (auto& task : tasks) {
totalActual += task[0];
}
int need = 0;
// 从后往前计算
for (int i = tasks.size() - 1; i >= 0; i--) {
need = max(need + tasks[i][0], tasks[i][1]);
}
return need;
}
};
class Solution:
def minimumEffort(self, tasks: List[List[int]]) -> int:
# 按照 minimum - actual 降序排列
tasks.sort(key=lambda x: x[1] - x[0], reverse=True)
need = 0
# 从后往前计算
for i in range(len(tasks) - 1, -1, -1):
actual, minimum = tasks[i]
need = max(need + actual, minimum)
return need
public class Solution {
public int MinimumEffort(int[][] tasks) {
// 按照 minimum - actual 降序排列
Array.Sort(tasks, (a, b) => (b[1] - b[0]).CompareTo(a[1] - a[0]));
int need = 0;
// 从后往前计算
for (int i = tasks.Length - 1; i >= 0; i--) {
int actual = tasks[i][0];
int minimum = tasks[i][1];
need = Math.Max(need + actual, minimum);
}
return need;
}
}
var minimumEffort = function(tasks) {
// 按照 minimum - actual 降序排列
tasks.sort((a, b) => (b[1] - b[0]) - (a[1] - a[0]));
let need = 0;
// 从后往前计算
for (let i = tasks.length - 1; i >= 0; i--) {
const [actual, minimum] = tasks[i];
need = Math.max(need + actual, minimum);
}
return need;
};
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 贪心排序 | O(n log n) | O(1) |
| 二分搜索 | O(n² log(sum)) | O(1) |
推荐解法:贪心排序,时间复杂度更优,实现简洁。