Facebook Pixel

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 engineer
  • efficiency[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:

  1. Including engineers with high speeds (to maximize the sum)
  2. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. 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).

  2. Among all engineers with efficiency ≥ e, we want the ones with the highest speeds to maximize our sum. This is because the performance formula is sum × min_efficiency, and once min_efficiency is fixed, we just need to maximize the sum.

  3. 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 need k-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:

  1. 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.

  2. Initialize variables:

    • ans: tracks the maximum performance found
    • tot: maintains the current sum of speeds in our team
    • h: min-heap to store speeds of current team members
    • mod: the modulo value 10^9 + 7
  3. Process each engineer as minimum efficiency:

    for s, e in t:

    For each engineer with speed s and efficiency e:

  4. 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
  5. 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.

  6. Return the result:

    return ans % mod

Why this works:

  • When we process engineer i with efficiency e[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 Evaluator

Example 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 takes O(n log n) time
  • Iterating through all n engineers takes O(n) time
  • For each engineer, we perform:
    • heappush() operation which takes O(log k) time in the worst case when heap size is k
    • heappop() operation (when heap size reaches k) which takes O(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 to O(n log n)

Space Complexity: O(n)

  • The sorted array t stores all n pairs of (speed, efficiency), requiring O(n) space
  • The min-heap h stores at most k elements, requiring O(k) space
  • Since k ≤ n, the total space complexity is O(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 on 1000000009

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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More