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 itcapital[i]
: the minimum capital required to start it
- You can complete at most
k
distinct projects
How it Works:
- To start a project, your current capital must be at least equal to that project's required capital
- When you complete a project, you gain its profit, which gets added to your total capital
- Each project can only be done once
- 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 affordh2
: A max-heap (using negative values) storing profits of affordable projects
The algorithm repeatedly:
- Moves all currently affordable projects from
h1
toh2
- Selects the project with maximum profit from
h2
- Adds that profit to current capital
- Continues until
k
projects are done or no affordable projects remain
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:
- Projects we cannot yet afford (waiting list)
- 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:
- Profits are pure gains (no costs involved)
- More capital never hurts - it only opens up more opportunities
- 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 capitalw
- If yes, pop it from
h1
and push its profit toh2
- 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 EvaluatorExample 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):
- 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
- State:
h1 = [(5,6)]
,h2 = [-5, -3, -1]
(stored as negatives for max-heap) - Select best project: pop -5 from h2, add 5 to capital
- New capital:
w = 2 + 5 = 7
Iteration 2 (k=2 remaining):
- Move affordable projects from h1 to h2:
- Project with capital=5 is affordable (5 ≤ 7), move profit=6 to h2
- State:
h1 = []
,h2 = [-6, -3, -1]
- Select best project: pop -6 from h2, add 6 to capital
- New capital:
w = 7 + 6 = 13
Iteration 3 (k=1 remaining):
- Move affordable projects: h1 is empty, nothing to move
- State:
h1 = []
,h2 = [-3, -1]
- Select best project: pop -3 from h2, add 3 to capital
- 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 takesO(n)
time heapify(h1)
takesO(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, contributingO(n log n)
total - Each
heappop
fromh1
takesO(log n)
time - Each
heappush
toh2
takesO(log n)
time - The second
heappop
fromh2
takesO(log n)
time
- The inner while loop can push elements to
- 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 alln
projects as tuples:O(n)
- The heap
h2
can contain at mostn
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
How does quick sort divide the problem into subproblems?
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!