The IPO problem on Leetcode asks you to maximize your capital by selecting up to k
projects to invest in, given a starting capital W
. Each project requires a certain amount of capital to start (given by the Capital
array) and yields a profit (given by the Profits
array) upon completion. You can only start a project if your current capital is at least the required capital for that project. After completing a project, you immediately add its profit to your capital and can use it for subsequent investments. Your goal is to pick at most k
projects in such a way that your final capital is maximized. Each project can be selected at most once.
k
different projects.Capital[i] ≤ current capital
).
At first glance, it might seem reasonable to try all combinations of k
projects to find the one with the highest final capital. However, this brute-force approach is computationally infeasible for large inputs because the number of combinations grows exponentially.
Instead, we need to make greedy choices: at every step, among all projects we can currently afford, we should pick the one with the highest profit. This ensures we maximize our capital at each stage, allowing us to afford more lucrative projects in the future. To do this efficiently, we need a way to quickly:
This leads us to consider priority queues (heaps) for efficient selection, and sorting to efficiently track which projects become affordable as our capital increases.
Let's break down the algorithm step by step:
k
selections:
k
iterations, return the final value of our capital.
We use a max-heap for profits because at each step we want the maximum profit among available projects. We sort by capital so we can efficiently "unlock" new projects as our capital grows.
Input:
k = 2
, W = 0
, Profits = [1,2,3]
, Capital = [0,1,1]
k
projects, leading to exponential time complexity: O(n^k)
.
O(n \log n)
O(n \log n)
O(n \log n + k \log n)
, which simplifies to O(n \log n)
O(n)
for the heap and project listThe IPO problem is a classic example of using a greedy strategy with the help of efficient data structures. By always picking the most profitable project we can afford and updating our available projects as our capital grows, we ensure we make the best local (and thus global) decisions. The use of sorting and a max-heap allows us to efficiently manage which projects are available and which one to pick next, resulting in an elegant and performant solution.
import heapq
class Solution:
def findMaximizedCapital(self, k, W, Profits, Capital):
projects = sorted(zip(Capital, Profits))
max_profit_heap = []
i = 0
n = len(Profits)
for _ in range(k):
while i < n and projects[i][0] <= W:
heapq.heappush(max_profit_heap, -projects[i][1])
i += 1
if not max_profit_heap:
break
W -= heapq.heappop(max_profit_heap)
return W
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
class Solution {
public:
int findMaximizedCapital(int k, int W, vector<int>& Profits, vector<int>& Capital) {
vector<pair<int, int>> projects;
int n = Profits.size();
for (int i = 0; i < n; ++i) {
projects.push_back({Capital[i], Profits[i]});
}
sort(projects.begin(), projects.end());
priority_queue<int> maxProfitHeap;
int idx = 0;
for (int i = 0; i < k; ++i) {
while (idx < n && projects[idx].first <= W) {
maxProfitHeap.push(projects[idx].second);
++idx;
}
if (maxProfitHeap.empty()) break;
W += maxProfitHeap.top();
maxProfitHeap.pop();
}
return W;
}
};
import java.util.*;
class Solution {
public int findMaximizedCapital(int k, int W, int[] Profits, int[] Capital) {
int n = Profits.length;
int[][] projects = new int[n][2];
for (int i = 0; i < n; i++) {
projects[i][0] = Capital[i];
projects[i][1] = Profits[i];
}
Arrays.sort(projects, (a, b) -> Integer.compare(a[0], b[0]));
PriorityQueue<Integer> maxProfitHeap = new PriorityQueue<>(Collections.reverseOrder());
int idx = 0;
for (int i = 0; i < k; i++) {
while (idx < n && projects[idx][0] <= W) {
maxProfitHeap.add(projects[idx][1]);
idx++;
}
if (maxProfitHeap.isEmpty()) break;
W += maxProfitHeap.poll();
}
return W;
}
}
class MaxHeap {
constructor() {
this.heap = [];
}
push(val) {
this.heap.push(val);
let idx = this.heap.length - 1;
while (idx > 0) {
let parent = Math.floor((idx - 1) / 2);
if (this.heap[parent] >= this.heap[idx]) break;
[this.heap[parent], this.heap[idx]] = [this.heap[idx], this.heap[parent]];
idx = parent;
}
}
pop() {
if (this.heap.length === 0) return null;
const top = this.heap[0];
const end = this.heap.pop();
if (this.heap.length === 0) return top;
this.heap[0] = end;
let idx = 0;
while (true) {
let left = 2 * idx + 1, right = 2 * idx + 2;
let largest = idx;
if (left < this.heap.length && this.heap[left] > this.heap[largest]) largest = left;
if (right < this.heap.length && this.heap[right] > this.heap[largest]) largest = right;
if (largest === idx) break;
[this.heap[largest], this.heap[idx]] = [this.heap[idx], this.heap[largest]];
idx = largest;
}
return top;
}
isEmpty() {
return this.heap.length === 0;
}
}
var findMaximizedCapital = function(k, W, Profits, Capital) {
let projects = Capital.map((c, i) => [c, Profits[i]]);
projects.sort((a, b) => a[0] - b[0]);
let heap = new MaxHeap();
let idx = 0, n = Profits.length;
for (let i = 0; i < k; i++) {
while (idx < n && projects[idx][0] <= W) {
heap.push(projects[idx][1]);
idx++;
}
if (heap.isEmpty()) break;
W += heap.pop();
}
return W;
};