Coding · Heap (priority queue)
Most capital from at most k projects
You start with capital w and may complete at most k projects,
one at a time. Project i can be started only if your current capital is at least
capital[i]; completing it adds profits[i] to your capital (the starting
requirement is not spent). Each project can be done at most once. Return the largest capital you can
end with.
Examples
| Input | Output | Why |
|---|---|---|
k = 2, w = 0, profits = [1, 2, 3], capital = [0, 1, 1] | 4 | Do project 0 (capital 1), then project 2 (capital 4). |
k = 1, w = 0, profits = [1, 2, 3], capital = [1, 1, 2] | 0 | Nothing is affordable at the start. |
k = 2, w = 1, profits = [5, 4, 1], capital = [1, 2, 6] | 10 | Project 0 unlocks project 1; project 2 is never worth it. |
Constraints
- 1 ≤ k ≤ 10⁵
- 0 ≤ w ≤ 10⁹
- 1 ≤ n ≤ 10⁵
- 0 ≤ profits[i] ≤ 10⁴
- 0 ≤ capital[i] ≤ 10⁹
Hints
Hint 1
Capital never decreases. So once a project becomes affordable, it stays affordable. What does that let you do with the list of projects?
Hint 2
At each step, among the projects you can afford, which one should you do? Why can taking it never hurt later?
Hint 3
Sort projects by capital required and walk a pointer along them as your capital grows, pushing each newly affordable profit into a max-heap. Each round, pop the largest profit.
Dry run on 3,2,[3,1,4,1,5],[3,0,2,6,9]
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 3projects by capital (need→profit)0→102→413→326→139→54profit heap (after pop)10round1capital6
Round 1: newly affordable: 0→1, 2→4. The heap offers profits 4, 1; take the largest, 4. Capital is now 6.
-
Step 2 / 3projects by capital (need→profit)0→102→413→326→139→54profit heap (after pop)1011round2capital9
Round 2: newly affordable: 3→3, 6→1. The heap offers profits 3, 1, 1; take the largest, 3. Capital is now 9.
-
Step 3 / 3projects by capital (need→profit)0→102→413→326→139→54profit heap (after pop)1011round3capital14
Round 3: newly affordable: 9→5. The heap offers profits 5, 1, 1; take the largest, 5. Capital is now 14. 3 projects done: the final capital is 14.
How to think about it
Two facts make this tractable. Profits are never negative, so capital only grows, and a project that is affordable now is affordable for ever. And among the affordable projects, taking the most profitable is always safe: it leaves you with at least as much capital as any other choice, so everything the other choice would have unlocked, it unlocks too.
So the algorithm needs two orderings at once:
- By capital required, to know which projects have just become affordable. Sort once, and
advance a pointer while
capital[j] ≤ w. - By profit, among the affordable ones, to pick the best. A max-heap holds their profits.
Each of k rounds pushes the newly unlocked profits and pops the largest. If the heap is empty, nothing is affordable and more rounds cannot change that: stop early. Sorting costs O(n log n) and each project is pushed and popped at most once, so the total is O(n log n).
The exchange argument is what interviewers want to hear: swap any other first choice for the most profitable affordable project and you end with at least as much capital at every later step.
Complexity. Time O(n log n) · Space O(n)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
import heapq
def ipo(k, w, profits, capital):
projects = sorted(zip(capital, profits)) # unlock order: cheapest requirement first
heap = [] # max-heap of affordable profits (negated)
j = 0
for _ in range(k):
while j < len(projects) and projects[j][0] <= w:
heapq.heappush(heap, -projects[j][1])
j += 1
if not heap:
break # nothing affordable; capital cannot grow
w -= heapq.heappop(heap) # take the most profitable
return w#include <vector>
#include <queue>
#include <algorithm>
#include <utility>
long long solve(int k, long long w, const std::vector<int>& profits, const std::vector<int>& capital) {
std::vector<std::pair<int, int>> projects; // (capital needed, profit)
for (size_t i = 0; i < profits.size(); ++i) projects.push_back({capital[i], profits[i]});
std::sort(projects.begin(), projects.end());
std::priority_queue<int> heap; // max-heap of affordable profits
size_t j = 0;
for (int round = 0; round < k; ++round) {
while (j < projects.size() && projects[j].first <= w) heap.push(projects[j++].second);
if (heap.empty()) break; // nothing affordable
w += heap.top(); heap.pop();
}
return w;
}class Solution {
public long ipo(int k, long w, int[] profits, int[] capital) {
int n = profits.length;
Integer[] order = new Integer[n];
for (int i = 0; i < n; i++) order[i] = i;
java.util.Arrays.sort(order, (a, b) -> Integer.compare(capital[a], capital[b]));
java.util.PriorityQueue<Integer> heap = new java.util.PriorityQueue<>(java.util.Collections.reverseOrder());
int j = 0;
for (int round = 0; round < k; round++) {
while (j < n && capital[order[j]] <= w) heap.add(profits[order[j++]]);
if (heap.isEmpty()) break; // nothing affordable
w += heap.poll(); // the most profitable affordable project
}
return w;
}
}function ipo(k, w, profits, capital) {
const order = profits.map((_, i) => i).sort((a, b) => capital[a] - capital[b]);
const h = []; // binary max-heap of affordable profits
const push = x => {
h.push(x); let i = h.length - 1;
while (i > 0) { const p = (i - 1) >> 1; if (h[p] >= h[i]) break; [h[p], h[i]] = [h[i], h[p]]; i = p; }
};
const pop = () => {
const top = h[0], last = h.pop();
if (h.length) {
h[0] = last; let i = 0;
for (;;) {
const l = 2 * i + 1, r = l + 1; let m = i;
if (l < h.length && h[l] > h[m]) m = l;
if (r < h.length && h[r] > h[m]) m = r;
if (m === i) break;
[h[m], h[i]] = [h[i], h[m]]; i = m;
}
}
return top;
};
let j = 0;
for (let round = 0; round < k; round++) {
while (j < order.length && capital[order[j]] <= w) push(profits[order[j++]]);
if (!h.length) break; // nothing affordable
w += pop(); // the most profitable affordable project
}
return w;
}use std::collections::BinaryHeap;
fn solve(k: usize, w: i64, profits: &[i64], capital: &[i64]) -> i64 {
let mut projects: Vec<(i64, i64)> = capital.iter().cloned().zip(profits.iter().cloned()).collect();
projects.sort(); // by capital needed
let mut heap = BinaryHeap::new(); // max-heap of affordable profits
let (mut w, mut j) = (w, 0usize);
for _ in 0..k {
while j < projects.len() && projects[j].0 <= w {
heap.push(projects[j].1);
j += 1;
}
match heap.pop() {
Some(best) => w += best,
None => break, // nothing affordable
}
}
w
}import Data.List (sortOn)
import qualified Data.Map.Strict as M
-- Projects sorted by capital needed; a Map from profit to count is the max-heap.
solve :: Int -> Int -> [Int] -> [Int] -> Int
solve k w profits capital = go k w (sortOn fst (zip capital profits)) M.empty
where
go 0 cash _ _ = cash
go left cash todo heap =
let (ready, later) = span ((<= cash) . fst) todo
heap' = foldr (\(_, p) -> M.insertWith (+) p 1) heap ready
in if M.null heap'
then cash -- nothing affordable
else let (best, heap'') = popMax heap'
in go (left - 1) (cash + best) later heap''
popMax m = case M.findMax m of
(v, 1) -> (v, M.delete v m)
(v, c) -> (v, M.insert v (c - 1) m)
Where people lose marks
- Re-scanning every project each round to find the affordable ones. That is O(nk), too slow at 10⁵ each.
- Using a min-heap of capital requirements only, without a profit heap: it finds what you can afford but not which is best.
- Forgetting to stop when nothing is affordable. With the heap empty, popping fails or the loop spins through the remaining k rounds.
- Subtracting the capital requirement when starting a project. The statement says it is a threshold, not a cost.
Variants to try
- Projects have a cost that is spent (profit is net of it): the same heap idea still works if the net gain is non-negative; with losses, the greedy exchange argument breaks.
- k is much larger than n: the loop ends after at most n rounds anyway.
- Return which projects were chosen: record the index alongside each profit in the heap.
Next problems
The k-th largest value
Keep only the k largest values seen so far. The smallest of them, on top of a min-heap, is the answer.
Open the problem → Medium15 minCheapest way to join ropes
Joining two ropes costs their combined length. Always join the two shortest, and a heap keeps finding them.
Open the problem →Write a solution and run it against the real test table.
Keep going
The k-th largest value
Keep only the k largest values seen so far. The smallest of them, on top of a min-heap, is the answer.
Open the problem → Medium15 minCheapest way to join ropes
Joining two ropes costs their combined length. Always join the two shortest, and a heap keeps finding them.
Open the problem → Heap (priority queue)The pattern behind it
Keep the few elements that matter in a structure that hands you the smallest (or largest) in O(1) and updates in O(log n).
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.