1383. Maximum Performance of a Team
Problem Description
You have n
engineers, where each engineer has two attributes:
speed[i]
: the speed of the i-th engineerefficiency[i]
: the efficiency of the i-th engineer
Your task is to select at most k
engineers to form a team that maximizes the team's performance.
The performance of a team is calculated as:
(sum of all selected engineers' speeds) × (minimum efficiency among selected engineers)
For example, if you select 3 engineers with speeds [10, 5, 8]
and efficiencies [4, 7, 2]
, the performance would be:
- Sum of speeds:
10 + 5 + 8 = 23
- Minimum efficiency:
min(4, 7, 2) = 2
- Performance:
23 × 2 = 46
You need to find the maximum possible performance by choosing the optimal subset of engineers (up to k
engineers). Since the result can be very large, return it modulo 10^9 + 7
.
The key insight is that for any team composition, the performance is bottlenecked by the engineer with the lowest efficiency. This means you need to carefully balance between:
- Including engineers with high speeds (to maximize the sum)
- Maintaining a reasonable minimum efficiency (since it multiplies the entire sum)
The challenge lies in finding the optimal trade-off between these two factors while respecting the constraint of selecting at most k
engineers.
Intuition
The key observation is that once we fix the minimum efficiency for our team, we want to maximize the sum of speeds. This suggests we should consider each engineer as a potential "minimum efficiency" candidate and build the best team around them.
Let's think about this step by step:
-
If we fix an engineer with efficiency
e
as our minimum, then we can only include engineers with efficiency≥ e
in our team (otherwise, they would become the new minimum and lower our efficiency factor). -
Among all engineers with efficiency
≥ e
, we want the ones with the highest speeds to maximize our sum. This is because the performance formula issum × min_efficiency
, and oncemin_efficiency
is fixed, we just need to maximize the sum. -
We should try each engineer as a potential minimum efficiency candidate. For each candidate, we build the best possible team that includes them.
This leads us to the following approach:
- Sort engineers by efficiency in descending order
- Process each engineer one by one as the potential minimum efficiency
- As we go through engineers in decreasing efficiency order, all previously seen engineers have higher or equal efficiency, so they're all valid team members
- Among all valid engineers seen so far, we keep track of the
k-1
fastest ones (we needk-1
because the current engineer must be included) - Use a min-heap to efficiently maintain the top
k
fastest engineers
The beauty of this approach is that by processing engineers in decreasing efficiency order, we naturally maintain the invariant that all engineers in our candidate pool have efficiency greater than or equal to the current minimum. We just need to greedily select the fastest ones among them.
When we have more than k
engineers in our pool, we remove the slowest one (smallest speed) to make room, ensuring we always have at most k
engineers with the highest possible speed sum for the current minimum efficiency.
Learn more about Greedy, Sorting and Heap (Priority Queue) patterns.
Solution Approach
Let's walk through the implementation step by step:
-
Create and sort engineer pairs: First, we combine speeds and efficiencies into tuples and sort them by efficiency in descending order:
t = sorted(zip(speed, efficiency), key=lambda x: -x[1])
This gives us engineers ordered from highest to lowest efficiency.
-
Initialize variables:
ans
: tracks the maximum performance foundtot
: maintains the current sum of speeds in our teamh
: min-heap to store speeds of current team membersmod
: the modulo value10^9 + 7
-
Process each engineer as minimum efficiency:
for s, e in t:
For each engineer with speed
s
and efficiencye
: -
Add current engineer to the team:
tot += s ans = max(ans, tot * e) heappush(h, s)
- Add their speed to the total sum
- Calculate performance with current engineer as minimum efficiency:
tot * e
- Update maximum performance if this is better
- Add the speed to our min-heap
-
Maintain team size constraint:
if len(h) == k: tot -= heappop(h)
- If we now have
k
engineers in our heap, we need to remove one for the next iteration - Remove the engineer with minimum speed (top of min-heap) to make room
- Subtract their speed from our total
This ensures that at any point, we have at most
k
engineers, and they are the fastest ones among all engineers with efficiency≥
current minimum. - If we now have
-
Return the result:
return ans % mod
Why this works:
- When we process engineer
i
with efficiencye[i]
, all previously seen engineers have efficiency≥ e[i]
- The heap contains the speeds of the fastest
min(k, number_seen)
engineers so far - By including the current engineer and calculating
tot * e[i]
, we're finding the best team with this engineer as the minimum efficiency - The min-heap efficiently maintains the top-k speeds, removing the slowest when needed
Time Complexity: O(n log n)
for sorting + O(n log k)
for heap operations = O(n log n)
Space Complexity: O(n)
for the sorted array + O(k)
for the heap
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with n = 4
engineers, k = 2
(select at most 2 engineers):
speed = [2, 10, 3, 1]
efficiency = [5, 4, 3, 9]
Step 1: Create and sort engineer pairs by efficiency (descending)
Engineers: [(1, 9), (2, 5), (10, 4), (3, 3)] (speed, efficiency)
Step 2: Process each engineer as potential minimum efficiency
Iteration 1: Engineer with speed=1, efficiency=9
- Add to team:
tot = 1
- Performance:
1 × 9 = 9
- Update
ans = 9
- Heap contains:
[1]
- Heap size < k, so no removal
Iteration 2: Engineer with speed=2, efficiency=5
- Available pool: engineers with efficiency ≥ 5 are
{1, 2}
- Add to team:
tot = 1 + 2 = 3
- Performance:
3 × 5 = 15
- Update
ans = 15
- Heap contains:
[1, 2]
- Heap size = k = 2, remove minimum speed (1):
tot = 3 - 1 = 2
- After removal, heap:
[2]
Iteration 3: Engineer with speed=10, efficiency=4
- Available pool: engineers with efficiency ≥ 4 are
{2, 10}
- Add to team:
tot = 2 + 10 = 12
- Performance:
12 × 4 = 48
- Update
ans = 48
- Heap contains:
[2, 10]
- Heap size = k = 2, remove minimum speed (2):
tot = 12 - 2 = 10
- After removal, heap:
[10]
Iteration 4: Engineer with speed=3, efficiency=3
- Available pool: engineers with efficiency ≥ 3 are
{10, 3}
- Add to team:
tot = 10 + 3 = 13
- Performance:
13 × 3 = 39
ans
remains 48 (not better)- Heap contains:
[3, 10]
Result: Maximum performance = 48 (achieved by selecting engineers with speeds [2, 10] and efficiencies [5, 4], where min efficiency = 4)
The key insight illustrated here: When we fixed efficiency=4 as minimum (iteration 3), we could choose from engineers with efficiencies [9, 5, 4], and we greedily picked the two fastest ones: speeds [2, 10], giving us the optimal performance of 48.
Solution Implementation
1from typing import List
2import heapq
3
4class Solution:
5 def maxPerformance(
6 self, n: int, speed: List[int], efficiency: List[int], k: int
7 ) -> int:
8 # Create pairs of (speed, efficiency) and sort by efficiency in descending order
9 engineers = sorted(zip(speed, efficiency), key=lambda x: -x[1])
10
11 max_performance = 0
12 total_speed = 0
13 MOD = 10**9 + 7
14
15 # Min heap to store speeds of selected engineers
16 # We use min heap to easily remove the engineer with minimum speed
17 speed_heap = []
18
19 # Iterate through engineers in decreasing order of efficiency
20 for current_speed, current_efficiency in engineers:
21 # Add current engineer's speed to total
22 total_speed += current_speed
23
24 # Calculate performance with current engineer as minimum efficiency
25 # Performance = sum of speeds * minimum efficiency
26 max_performance = max(max_performance, total_speed * current_efficiency)
27
28 # Add current speed to heap
29 heapq.heappush(speed_heap, current_speed)
30
31 # If we have k engineers, remove the one with minimum speed
32 # to make room for potentially better combinations
33 if len(speed_heap) == k:
34 total_speed -= heapq.heappop(speed_heap)
35
36 return max_performance % MOD
37
1class Solution {
2 private static final int MOD = (int) 1e9 + 7;
3
4 public int maxPerformance(int n, int[] speed, int[] efficiency, int k) {
5 // Create a 2D array to store speed and efficiency pairs
6 int[][] engineers = new int[n][2];
7 for (int i = 0; i < n; i++) {
8 engineers[i] = new int[] {speed[i], efficiency[i]};
9 }
10
11 // Sort engineers by efficiency in descending order
12 // This ensures we process higher efficiency values first
13 Arrays.sort(engineers, (a, b) -> b[1] - a[1]);
14
15 // Min heap to track the k engineers with highest speeds
16 // We use min heap to easily remove the engineer with lowest speed
17 PriorityQueue<Integer> speedHeap = new PriorityQueue<>();
18
19 // Total sum of speeds in current team
20 long totalSpeed = 0;
21
22 // Maximum performance found so far
23 long maxPerformance = 0;
24
25 // Iterate through engineers sorted by efficiency (high to low)
26 for (int[] engineer : engineers) {
27 int currentSpeed = engineer[0];
28 int currentEfficiency = engineer[1];
29
30 // Add current engineer's speed to total
31 totalSpeed += currentSpeed;
32
33 // Calculate performance with current engineer as minimum efficiency
34 // Performance = sum of speeds * minimum efficiency
35 maxPerformance = Math.max(maxPerformance, totalSpeed * currentEfficiency);
36
37 // Add current speed to heap
38 speedHeap.offer(currentSpeed);
39
40 // If we have more than k engineers, remove the one with lowest speed
41 if (speedHeap.size() == k) {
42 totalSpeed -= speedHeap.poll();
43 }
44 }
45
46 // Return the result modulo MOD
47 return (int) (maxPerformance % MOD);
48 }
49}
50
1class Solution {
2public:
3 int maxPerformance(int n, vector<int>& speed, vector<int>& efficiency, int k) {
4 // Create pairs of (efficiency, speed) for each engineer
5 // Store negative efficiency for sorting in descending order
6 vector<pair<int, int>> engineers(n);
7 for (int i = 0; i < n; ++i) {
8 engineers[i] = {-efficiency[i], speed[i]};
9 }
10
11 // Sort engineers by efficiency in descending order (due to negative values)
12 sort(engineers.begin(), engineers.end());
13
14 // Min heap to maintain top k speeds
15 priority_queue<int, vector<int>, greater<int>> minHeap;
16
17 // Variables to track maximum performance and current sum of speeds
18 long long maxPerf = 0;
19 long long speedSum = 0;
20 const int MOD = 1e9 + 7;
21
22 // Iterate through engineers sorted by efficiency (highest to lowest)
23 for (const auto& engineer : engineers) {
24 int currentSpeed = engineer.second;
25 int currentEfficiency = -engineer.first; // Convert back to positive
26
27 // Add current engineer's speed to the sum
28 speedSum += currentSpeed;
29
30 // Calculate performance with current engineer as minimum efficiency
31 // Performance = sum of speeds * minimum efficiency in the team
32 maxPerf = max(maxPerf, speedSum * currentEfficiency);
33
34 // Add current speed to the heap
35 minHeap.push(currentSpeed);
36
37 // If we have more than k engineers, remove the one with lowest speed
38 if (minHeap.size() == k) {
39 speedSum -= minHeap.top();
40 minHeap.pop();
41 }
42 }
43
44 // Return the maximum performance modulo MOD
45 return static_cast<int>(maxPerf % MOD);
46 }
47};
48
1function maxPerformance(n: number, speed: number[], efficiency: number[], k: number): number {
2 // Create pairs of (efficiency, speed) for each engineer
3 // We'll sort by efficiency in descending order
4 const engineers: [number, number][] = [];
5 for (let i = 0; i < n; i++) {
6 engineers.push([efficiency[i], speed[i]]);
7 }
8
9 // Sort engineers by efficiency in descending order
10 engineers.sort((a, b) => b[0] - a[0]);
11
12 // Min heap to maintain top k speeds
13 // Using array with manual heap operations since TypeScript doesn't have built-in heap
14 const minHeap: number[] = [];
15
16 // Helper functions for heap operations
17 const heapPush = (heap: number[], value: number): void => {
18 heap.push(value);
19 let currentIndex = heap.length - 1;
20
21 // Bubble up
22 while (currentIndex > 0) {
23 const parentIndex = Math.floor((currentIndex - 1) / 2);
24 if (heap[currentIndex] >= heap[parentIndex]) break;
25
26 // Swap with parent
27 [heap[currentIndex], heap[parentIndex]] = [heap[parentIndex], heap[currentIndex]];
28 currentIndex = parentIndex;
29 }
30 };
31
32 const heapPop = (heap: number[]): number => {
33 if (heap.length === 0) return 0;
34 if (heap.length === 1) return heap.pop()!;
35
36 const minValue = heap[0];
37 heap[0] = heap.pop()!;
38
39 // Bubble down
40 let currentIndex = 0;
41 while (true) {
42 const leftChild = 2 * currentIndex + 1;
43 const rightChild = 2 * currentIndex + 2;
44 let smallest = currentIndex;
45
46 if (leftChild < heap.length && heap[leftChild] < heap[smallest]) {
47 smallest = leftChild;
48 }
49 if (rightChild < heap.length && heap[rightChild] < heap[smallest]) {
50 smallest = rightChild;
51 }
52
53 if (smallest === currentIndex) break;
54
55 // Swap with smallest child
56 [heap[currentIndex], heap[smallest]] = [heap[smallest], heap[currentIndex]];
57 currentIndex = smallest;
58 }
59
60 return minValue;
61 };
62
63 // Variables to track maximum performance and current sum of speeds
64 let maxPerf = 0n; // Using BigInt for large number handling
65 let speedSum = 0n;
66 const MOD = BigInt(1e9 + 7);
67
68 // Iterate through engineers sorted by efficiency (highest to lowest)
69 for (const [currentEfficiency, currentSpeed] of engineers) {
70 // Add current engineer's speed to the sum
71 speedSum += BigInt(currentSpeed);
72
73 // Calculate performance with current engineer as minimum efficiency
74 // Performance = sum of speeds * minimum efficiency in the team
75 const currentPerf = speedSum * BigInt(currentEfficiency);
76 if (currentPerf > maxPerf) {
77 maxPerf = currentPerf;
78 }
79
80 // Add current speed to the heap
81 heapPush(minHeap, currentSpeed);
82
83 // If we have more than k engineers, remove the one with lowest speed
84 if (minHeap.length === k) {
85 const removedSpeed = heapPop(minHeap);
86 speedSum -= BigInt(removedSpeed);
87 }
88 }
89
90 // Return the maximum performance modulo MOD
91 return Number(maxPerf % MOD);
92}
93
Time and Space Complexity
Time Complexity: O(n log n + n log k)
- Sorting the array of
(speed, efficiency)
pairs by efficiency in descending order takesO(n log n)
time - Iterating through all
n
engineers takesO(n)
time - For each engineer, we perform:
heappush()
operation which takesO(log k)
time in the worst case when heap size isk
heappop()
operation (when heap size reachesk
) which takesO(log k)
time
- Overall:
O(n log n)
for sorting +O(n log k)
for heap operations =O(n log n + n log k)
- Since
k ≤ n
, this simplifies toO(n log n)
Space Complexity: O(n)
- The sorted array
t
stores alln
pairs of(speed, efficiency)
, requiringO(n)
space - The min-heap
h
stores at mostk
elements, requiringO(k)
space - Since
k ≤ n
, the total space complexity isO(n)
dominated by the sorted array
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-One Error in Team Size Management
The Pitfall:
A common mistake is removing an engineer from the heap before calculating the performance with the current engineer. This leads to never actually considering teams of size k
.
Incorrect Implementation:
for current_speed, current_efficiency in engineers:
# WRONG: Removing before calculation
if len(speed_heap) == k:
total_speed -= heapq.heappop(speed_heap)
total_speed += current_speed
max_performance = max(max_performance, total_speed * current_efficiency)
heapq.heappush(speed_heap, current_speed)
Why It's Wrong:
- When we have
k-1
engineers and add the k-th one, we immediately remove one before calculating performance - We never actually evaluate a team with exactly
k
engineers - This results in suboptimal answers
Correct Approach:
for current_speed, current_efficiency in engineers:
# Add current engineer first
total_speed += current_speed
heapq.heappush(speed_heap, current_speed)
# Calculate performance with current team (including new engineer)
max_performance = max(max_performance, total_speed * current_efficiency)
# THEN remove if we exceed k engineers
if len(speed_heap) > k: # Note: > k, not == k
total_speed -= heapq.heappop(speed_heap)
2. Forgetting to Apply Modulo at the Right Time
The Pitfall: Applying modulo during intermediate calculations instead of only at the final return.
Incorrect Implementation:
# WRONG: Applying modulo too early
max_performance = max(max_performance, (total_speed * current_efficiency) % MOD)
Why It's Wrong:
- Modulo operation changes the actual value
- When comparing
max(a % MOD, b % MOD)
, you might get incorrect results - Example:
max(1000000009 % MOD, 1000000008 % MOD)
=max(2, 1)
=2
, but the actual maximum should be based on1000000009
Correct Approach:
# Keep track of actual maximum
max_performance = max(max_performance, total_speed * current_efficiency)
# Apply modulo only when returning
return max_performance % MOD
3. Not Handling Edge Cases Properly
The Pitfall:
Not considering cases where k >= n
or when we should select fewer than k
engineers.
Why It Matters:
- When
k >= n
, we might want to select all engineers, or just a subset - The algorithm should naturally handle this by considering all possible team sizes up to
k
- The current implementation handles this correctly by processing all engineers and only removing when heap size exceeds
k
Verification: The solution correctly handles these cases because:
- It considers every possible minimum efficiency
- For each minimum efficiency, it maintains the best possible team of size ≤ k
- It doesn't force exactly
k
engineers; it finds the optimal size ≤ k
A person thinks of a number between 1 and 1000. You may ask any number questions to them, provided that the question can be answered with either "yes" or "no".
What is the minimum number of questions you needed to ask so that you are guaranteed to know the number that the person is thinking?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
Want a Structured Path to Master System Design Too? Don’t Miss This!