Facebook Pixel

502. IPO

Problem Description

You are helping LeetCode prepare for its IPO by selecting projects to maximize capital. Here's the scenario:

Initial Setup:

  • You start with w amount of capital
  • You have n available projects
  • Each project i has:
    • profits[i]: the pure profit you'll gain after completing it
    • capital[i]: the minimum capital required to start it
  • You can complete at most k distinct projects

How it Works:

  1. To start a project, your current capital must be at least equal to that project's required capital
  2. When you complete a project, you gain its profit, which gets added to your total capital
  3. Each project can only be done once
  4. The capital requirement is just a threshold - it's not spent or consumed

Goal: Select and complete at most k projects to maximize your final capital.

Example Flow:

  • Start with capital = 10
  • Project A requires capital 5, gives profit 3
  • Project B requires capital 10, gives profit 5
  • If you do Project A first: capital becomes 10 + 3 = 13
  • Now you can do Project B: capital becomes 13 + 5 = 18

The solution uses a greedy approach with two heaps:

  • h1: A min-heap storing (capital_required, profit) pairs, allowing quick access to projects you can afford
  • h2: A max-heap (using negative values) storing profits of affordable projects

The algorithm repeatedly:

  1. Moves all currently affordable projects from h1 to h2
  2. Selects the project with maximum profit from h2
  3. Adds that profit to current capital
  4. Continues until k projects are done or no affordable projects remain
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that once we have enough capital to start a project, there's no reason to delay it if it offers the highest profit among available options. This naturally leads to a greedy strategy.

Think about it this way: at any point in time, we have a certain amount of capital. Some projects are accessible (we meet their capital requirements), while others are not. Among the accessible projects, which should we choose? Since completing a project only increases our capital (profit is always added), we should pick the one with the maximum profit. This maximizes our capital growth and potentially unlocks more projects.

The challenge is efficiently tracking two groups of projects:

  1. Projects we cannot yet afford (waiting list)
  2. Projects we can afford (available pool)

As our capital grows after each project completion, some projects move from the "cannot afford" group to the "can afford" group. We need to:

  • Quickly identify which projects become affordable when our capital increases
  • Quickly find the maximum profit among all affordable projects

This dual requirement suggests using two heaps:

  • A min-heap sorted by capital requirement helps us efficiently find all projects that become affordable when our capital reaches a certain threshold (we can pop all projects with capital <= w)
  • A max-heap sorted by profit helps us quickly select the most profitable project from our available options

The algorithm mirrors real-world investment decisions: with limited resources and opportunities, you always pick the best available option that you can afford, use the returns to expand your capabilities, then repeat. The greedy choice (always picking maximum profit from available options) is optimal because:

  1. Profits are pure gains (no costs involved)
  2. More capital never hurts - it only opens up more opportunities
  3. We want to maximize final capital, so maximizing at each step makes sense

Learn more about Greedy, Sorting and Heap (Priority Queue) patterns.

Solution Approach

The implementation uses two heaps to efficiently manage project selection:

Step 1: Initialize the Min-Heap

h1 = [(c, p) for c, p in zip(capital, profits)]
heapify(h1)

We create a min-heap h1 containing tuples of (capital_required, profit) for all projects. This heap is ordered by capital requirement, allowing us to quickly access projects with the lowest capital requirements first.

Step 2: Initialize the Max-Heap

h2 = []

We create an empty list h2 that will serve as a max-heap for storing profits of affordable projects.

Step 3: Main Selection Loop

while k:

We iterate up to k times, as we can select at most k projects.

Step 4: Move Affordable Projects

while h1 and h1[0][0] <= w:
    heappush(h2, -heappop(h1)[1])

This inner loop transfers all newly affordable projects from h1 to h2:

  • Check if the project with minimum capital requirement (h1[0][0]) is affordable with current capital w
  • If yes, pop it from h1 and push its profit to h2
  • We negate the profit value (-heappop(h1)[1]) because Python's heapq is a min-heap by default, so negating values creates a max-heap effect

Step 5: Select Best Project

if not h2:
    break
w -= heappop(h2)
k -= 1
  • If no affordable projects exist (h2 is empty), we break early
  • Otherwise, pop the maximum profit from h2 (remember it's stored as negative)
  • Add this profit to our capital: w -= heappop(h2) (subtracting a negative value adds it)
  • Decrement the project counter k

Step 6: Return Final Capital

return w

After completing up to k projects or running out of affordable options, return the final capital.

Time Complexity: O(n log n) where n is the number of projects

  • Initial heapify: O(n)
  • Each project can be pushed/popped from heaps at most once: O(n log n)

Space Complexity: O(n) for storing the two heaps

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example to see how the two-heap solution works.

Input:

  • Initial capital w = 2
  • Maximum projects k = 3
  • Projects:
    • Project 0: capital required = 1, profit = 3
    • Project 1: capital required = 2, profit = 5
    • Project 2: capital required = 5, profit = 6
    • Project 3: capital required = 0, profit = 1

Step-by-step execution:

Initial Setup:

  • h1 (min-heap by capital): [(0,1), (1,3), (2,5), (5,6)]
  • h2 (max-heap for profits): []
  • Current capital: w = 2

Iteration 1 (k=3 remaining):

  1. Move affordable projects from h1 to h2:
    • Project with capital=0 is affordable (0 ≤ 2), move profit=1 to h2
    • Project with capital=1 is affordable (1 ≤ 2), move profit=3 to h2
    • Project with capital=2 is affordable (2 ≤ 2), move profit=5 to h2
    • Project with capital=5 is NOT affordable (5 > 2), stop
  2. State: h1 = [(5,6)], h2 = [-5, -3, -1] (stored as negatives for max-heap)
  3. Select best project: pop -5 from h2, add 5 to capital
  4. New capital: w = 2 + 5 = 7

Iteration 2 (k=2 remaining):

  1. Move affordable projects from h1 to h2:
    • Project with capital=5 is affordable (5 ≤ 7), move profit=6 to h2
  2. State: h1 = [], h2 = [-6, -3, -1]
  3. Select best project: pop -6 from h2, add 6 to capital
  4. New capital: w = 7 + 6 = 13

Iteration 3 (k=1 remaining):

  1. Move affordable projects: h1 is empty, nothing to move
  2. State: h1 = [], h2 = [-3, -1]
  3. Select best project: pop -3 from h2, add 3 to capital
  4. New capital: w = 13 + 3 = 16

Result: Final capital = 16 (started with 2, gained profits of 5+6+3=14)

The algorithm efficiently selected projects in order of maximum available profit: first the project with profit=5, then profit=6 (which became available after gaining capital), then profit=3. The project with profit=1 was never selected because better options were always available.

Solution Implementation

1class Solution:
2    def findMaximizedCapital(
3        self, k: int, w: int, profits: List[int], capital: List[int]
4    ) -> int:
5        from heapq import heapify, heappush, heappop
6        from typing import List
7      
8        # Create min heap of (capital_required, profit) pairs
9        # This heap stores projects we haven't started yet
10        available_projects = [(c, p) for c, p in zip(capital, profits)]
11        heapify(available_projects)
12      
13        # Max heap for profits of doable projects (using negative values for max heap)
14        doable_projects = []
15      
16        # Try to complete up to k projects
17        while k > 0:
18            # Move all projects we can afford from available to doable heap
19            while available_projects and available_projects[0][0] <= w:
20                capital_required, profit = heappop(available_projects)
21                # Push negative profit to simulate max heap
22                heappush(doable_projects, -profit)
23          
24            # If no doable projects, we can't proceed further
25            if not doable_projects:
26                break
27          
28            # Select the project with maximum profit
29            max_profit = -heappop(doable_projects)
30            w += max_profit  # Add profit to our capital
31            k -= 1  # Decrement remaining project count
32      
33        return w
34
1class Solution {
2    public int findMaximizedCapital(int k, int w, int[] profits, int[] capital) {
3        int n = capital.length;
4      
5        // Min heap to store projects sorted by capital requirement
6        // Each element is [capital required, profit]
7        PriorityQueue<int[]> minCapitalHeap = new PriorityQueue<>((a, b) -> a[0] - b[0]);
8      
9        // Add all projects to the min heap
10        for (int i = 0; i < n; i++) {
11            minCapitalHeap.offer(new int[] {capital[i], profits[i]});
12        }
13      
14        // Max heap to store available projects sorted by profit (descending)
15        PriorityQueue<Integer> maxProfitHeap = new PriorityQueue<>((a, b) -> b - a);
16      
17        // Select up to k projects
18        while (k-- > 0) {
19            // Move all affordable projects from minCapitalHeap to maxProfitHeap
20            while (!minCapitalHeap.isEmpty() && minCapitalHeap.peek()[0] <= w) {
21                maxProfitHeap.offer(minCapitalHeap.poll()[1]);
22            }
23          
24            // If no affordable projects available, stop early
25            if (maxProfitHeap.isEmpty()) {
26                break;
27            }
28          
29            // Select the most profitable project and add its profit to capital
30            w += maxProfitHeap.poll();
31        }
32      
33        return w;
34    }
35}
36
1using pii = pair<int, int>;
2
3class Solution {
4public:
5    int findMaximizedCapital(int k, int w, vector<int>& profits, vector<int>& capital) {
6        // Min heap to store projects as pairs of (capital required, profit)
7        // Projects are sorted by capital requirement in ascending order
8        priority_queue<pii, vector<pii>, greater<pii>> minCapitalHeap;
9      
10        // Build the min heap with all projects
11        int n = profits.size();
12        for (int i = 0; i < n; ++i) {
13            minCapitalHeap.push({capital[i], profits[i]});
14        }
15      
16        // Max heap to store available project profits
17        // We want to select the project with maximum profit among available ones
18        priority_queue<int> maxProfitHeap;
19      
20        // Select up to k projects
21        while (k--) {
22            // Move all projects that can be started with current capital to maxProfitHeap
23            // These are projects where required capital <= current capital w
24            while (!minCapitalHeap.empty() && minCapitalHeap.top().first <= w) {
25                maxProfitHeap.push(minCapitalHeap.top().second);
26                minCapitalHeap.pop();
27            }
28          
29            // If no projects are available, we cannot proceed further
30            if (maxProfitHeap.empty()) {
31                break;
32            }
33          
34            // Select the project with maximum profit and add it to our capital
35            w += maxProfitHeap.top();
36            maxProfitHeap.pop();
37        }
38      
39        // Return the final capital after completing selected projects
40        return w;
41    }
42};
43
1type ProjectPair = [number, number]; // [capital, profit]
2
3function findMaximizedCapital(k: number, w: number, profits: number[], capital: number[]): number {
4    // Min heap to store projects as pairs of (capital required, profit)
5    // Projects are sorted by capital requirement in ascending order
6    const minCapitalHeap: ProjectPair[] = [];
7  
8    // Build the min heap with all projects
9    const n = profits.length;
10    for (let i = 0; i < n; i++) {
11        minCapitalHeap.push([capital[i], profits[i]]);
12    }
13  
14    // Sort as min heap by capital requirement
15    minCapitalHeap.sort((a, b) => a[0] - b[0]);
16  
17    // Max heap to store available project profits
18    // We want to select the project with maximum profit among available ones
19    const maxProfitHeap: number[] = [];
20  
21    // Select up to k projects
22    while (k-- > 0) {
23        // Move all projects that can be started with current capital to maxProfitHeap
24        // These are projects where required capital <= current capital w
25        while (minCapitalHeap.length > 0 && minCapitalHeap[0][0] <= w) {
26            const project = minCapitalHeap.shift()!;
27            // Insert into max heap (using negative for max heap simulation)
28            maxProfitHeap.push(project[1]);
29            maxProfitHeap.sort((a, b) => b - a);
30        }
31      
32        // If no projects are available, we cannot proceed further
33        if (maxProfitHeap.length === 0) {
34            break;
35        }
36      
37        // Select the project with maximum profit and add it to our capital
38        w += maxProfitHeap.shift()!;
39    }
40  
41    // Return the final capital after completing selected projects
42    return w;
43}
44

Time and Space Complexity

Time Complexity: O(n log n + k log n) where n is the number of projects and k is the maximum number of projects that can be selected.

  • Creating the list h1 with tuples takes O(n) time
  • heapify(h1) takes O(n) time to build the min-heap
  • The main while loop runs at most k iterations
  • In each iteration:
    • The inner while loop can push elements to h2, and across all iterations, each element is pushed at most once, contributing O(n log n) total
    • Each heappop from h1 takes O(log n) time
    • Each heappush to h2 takes O(log n) time
    • The second heappop from h2 takes O(log n) time
  • Overall: O(n + n + n log n + k log n) = O(n log n + k log n)

Space Complexity: O(n)

  • The list h1 stores all n projects as tuples: O(n)
  • The heap h2 can contain at most n elements in the worst case: O(n)
  • Total auxiliary space: O(n) + O(n) = O(n)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Misunderstanding Capital Requirements as Costs

Many people initially think that the capital requirement is "spent" when starting a project, similar to an investment. This leads to incorrect implementations where they subtract the capital requirement from the current capital.

Incorrect approach:

# WRONG: Subtracting capital requirement
w = w - capital_required + profit  # This is incorrect!

Correct approach:

# RIGHT: Capital is just a threshold, only add profit
w = w + profit  # Capital requirement is not consumed

2. Using Wrong Heap Order for Profits

Python's heapq module only provides min-heap functionality. Forgetting to negate values when creating a max-heap for profits will result in selecting the least profitable projects instead of the most profitable ones.

Incorrect approach:

# WRONG: This creates a min-heap, selecting minimum profits
heappush(doable_projects, profit)  # Will pick smallest profit!
best_profit = heappop(doable_projects)

Correct approach:

# RIGHT: Negate to simulate max-heap
heappush(doable_projects, -profit)  # Store as negative
best_profit = -heappop(doable_projects)  # Negate back when using

3. Not Checking for Empty Heaps Before Popping

Attempting to pop from an empty heap will raise an exception. This commonly happens when there are no affordable projects left but k > 0.

Incorrect approach:

# WRONG: May crash if no affordable projects exist
while k > 0:
    # Move affordable projects...
    max_profit = -heappop(doable_projects)  # IndexError if empty!
    k -= 1

Correct approach:

# RIGHT: Check if heap is empty before popping
while k > 0:
    # Move affordable projects...
    if not doable_projects:
        break  # Exit early if no doable projects
    max_profit = -heappop(doable_projects)
    k -= 1

4. Inefficient Re-checking of All Projects

A naive approach might re-scan all remaining projects after each selection to find newly affordable ones, leading to O(n²) time complexity.

Incorrect approach:

# WRONG: O(n*k) complexity, checking all projects repeatedly
for _ in range(k):
    affordable = []
    for i, cap in enumerate(capital):
        if not used[i] and cap <= w:
            affordable.append((profits[i], i))
    # Select best from affordable...

Correct approach:

# RIGHT: Use heap to efficiently track next affordable project
# Only check projects once when they become affordable
while available_projects and available_projects[0][0] <= w:
    capital_required, profit = heappop(available_projects)
    heappush(doable_projects, -profit)

5. Not Handling Edge Cases

Failing to handle cases where k is larger than the number of available projects or when initial capital is insufficient for any project.

Solution: The provided code naturally handles these cases:

  • If k > number of projects: The loop exits when no more affordable projects exist
  • If initial capital is too low: The doable_projects heap remains empty and the function returns the initial capital
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

How does quick sort divide the problem into subproblems?


Recommended Readings

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

Load More