Leetcode 1383 最大的团队表现值(贪心)
题目
公司有编号为 1 到 n 的 n 个工程师,给你两个数组 speed 和 efficiency ,其中 speed[i] 和 efficiency[i] 分别代表第 i 位工程师的速度和效率。请你返回由最多 k 个工程师组成的 最大团队表现值 ,由于答案可能很大,请你返回结果对 10^9 + 7 取余后的结果。
团队表现值 的定义为:一个团队中「所有工程师速度的和」乘以他们「效率值中的最小值」。
示例 1:
输入:n = 6, speed = [2,10,3,1,5,8], efficiency = [5,4,3,9,7,2], k = 2
输出:60
解释:
我们选择工程师 2(speed=10 且 efficiency=4)和工程师 5(speed=5 且 efficiency=7)。他们的团队表现值为 performance = (10 + 5) * min(4, 7) = 60 。
示例 2:
输入:n = 6, speed = [2,10,3,1,5,8], efficiency = [5,4,3,9,7,2], k = 3
输出:68
解释:
此示例与第一个示例相同,除了 k = 3 。我们可以选择工程师 1 ,工程师 2 和工程师 5 得到最大的团队表现值。表现值为 performance = (2 + 10 + 5) * min(5, 4, 7) = 68 。
示例 3:
输入:n = 6, speed = [2,10,3,1,5,8], efficiency = [5,4,3,9,7,2], k = 4
输出:72
提示:
1 <= n <= 10^5
speed.length == n
efficiency.length == n
1 <= speed[i] <= 10^5
1 <= efficiency[i] <= 10^8
1 <= k <= n
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/maximum-performance-of-a-team
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题
首先,对题目进行抽象,即求解这么一个问题:
有 n n n个元素,每个元素都有两个属性,属性A、属性B
现在要从中取 n n n个元素,求一个策略,要求该策略满足:
k k k个元素属性 A A A之和乘以属性 B B B的最小值得到的值最大
进行梳理,发现即满足以下关系式:
F = ∑ ( E i . A ) ∗ m i n { E i . B } , E i ∈ K , K ∈ N F = \sum(E_i.A) * min\{E_i.B\},E_i\in K, K\in N F=∑(Ei.A)∗min{
Ei.B},Ei∈K,K∈N
其中 N N N是全集, K K K是元素选取集合, E i E_i Ei是 K K K中的元素,求 F F F的最大值
这是一个双变量求最值问题, X 1 = ∑ ( E i . A ) X_1=\sum(E_i.A) X1=∑(Ei.A), X 2 = m i n { E i . B } X_2=min\{E_i.B\} X2=min{ Ei.B}, F = X 1 ∗ X 2 F=X_1*X_2 F=X1∗X2, X 1 X_1 X1、 X 2 X_2 X2是非线性相关的
求解此类问题,可以采取先固定一个变量、再选取另一个变量的方法
例如,固定住 X 2 X_2 X2,则 K K K就受到了限制,此时就可以轻易找到对应的 X 1 X_1 X1,从而确定最优的 K K K。
现在的问题是,有两个变量,为什么要选择固定 X 2 X_2 X2而非 X 1 X_1 X1
原因之一是,给出 K K K能很容易找到对应的 X 1 X_1 X1、 X 2 X_2 X2,而给出 X 1 X_1 X1后却很难枚举所有符合条件的 K K K。
另一个原因是,对于容量为 n n n的集合,任意取 k k k个数,最小值的可能取值最多就 k k k个,而和的取值就不一定了。因此,枚举所有的最小值是很容易的,而枚举所有的和就不容易了
因此,题目的大体思路是:
枚举所有的最小效率,并求出对应的最小效率下的最优解,然后取全局最优解
伪代码:
sort(N) // 对集合N进行排序,使其有序,按效率从大到小排序
Val = 0
totalA = 0
K = NULL
for E in N: // 由于效率从大到小排序,可以保证最小效率就是当前的元素的效率
totalA -= min(K.A)
totalA += E // 求当前效率下的最大速度
K.push(E) // 将E加入集合K
newVal = totalA * E.B
Val = max{newVal, Val} // 更新全局最优解
return Val
第二个问题是,用什么样的数据结构进行解题
由于每次都需要做:取出最小值,放入新值
的操作,如果是用数组进行存储,势必要进行大量的移位操作。注意到只需要取出最小值,因此可以维护一个小根堆——小根堆的堆顶必定为最小值,每次都从小根堆的堆顶取出旧值,再push新值
实现代码:
int minHeapPush(int *arr, int size, int e) // 向小根堆中插入元素,同时删除堆顶
{
int res = arr[0]; // 根节点是最小的,因为是小根堆
arr[0] = e;
int root = 0, child = 1;
while (child < size)
{
if (child + 1 < size && arr[child] > arr[child + 1])
child += 1;
if (arr[root] < arr[child])
return res;
int tmp = arr[root];
arr[root] = arr[child];
arr[child] = tmp;
root = child;
child = root * 2 + 1;
}
return res;
}
int maxPerformance(int n, int* speed, int speedSize, int* efficiency, int efficiencySize, int k)
{
// 贪心加堆
// 第一步,对效率进行排序,使用希尔排序
for (int gap = n / 2; gap > 0; gap /= 2)
for (int i = gap; i < n; i ++)
for (int j = i - gap; j >= 0 && efficiency[j] < efficiency[j + gap]; j -= gap)
{
int tmp_1 = efficiency[j], tmp_2 = speed[j];
efficiency[j] = efficiency[j + gap]; speed[j] = speed[j + gap];
efficiency[j + gap] = tmp_1; speed[j + gap] = tmp_2;
}
// 第二步,遍历效率数组,同时维护一个小根堆
int heap[k];
memset(heap, 0, sizeof(int) * k);
long long performance = 0;
long long totalSpeed = 0;
for (int i = 0; i < n; i ++)
{
// 效率递减
totalSpeed += -minHeapPush(heap, k, speed[i]) + speed[i]; // 减掉一个最小的,添加一个新的
long long tmpPerformance = (long long)totalSpeed * efficiency[i];
performance = (tmpPerformance > performance) ? tmpPerformance : performance;
}
return (int)(performance % (1000000007L));
}
运行结果:
**53 / 53** 个通过测试用例
状态:_通过_
执行用时: **124 ms**
内存消耗: **11.2 MB**
运行速度瓶颈分析:
- 使用了希尔排序,且同时对两个数组进行交换,耗费的时间大概是别人的两倍多一点
minHeapPush
是一个使用很频繁的函数,如果使用inline
关键字或者将其并入主函数,运行速度能得到很大的提升
碰到的问题:
- 完全没有思路,看了官方题解才知道怎么写
- 忘记了堆排序和shell排序怎么写了
- 对于可能溢出
int
范围的数字处理缺少经验