IPO solution and explanation
9 more videos on this problem
IPO - Leetcode 502 - PythonNeetCodeIO · 11:57 · 36K views
IPO | Easy Beginner Friendly | Story To Code | Leetcode 502 | codestorywithMIKcodestorywithMIK · 29:52 · 16K views
4. Two Heap | Leetcode | 502 | IPO | Maximise CapitalImran Sarwar · 16:40 · 5K views
IPO | Leetcode 502 | HeapsAyushi Sharma · 17:50 · 5K views
502. IPO | Greedy | Priority Queue | SortingAryan Mittal · 23:57 · 5K views
502 IPO | Sorting | Greedy | LeetCode Hard | LeetCode Problem of the day | LeetCode POTDDeep Tech · 14:14 · 2K views
🔥Leetcode 502. IPO | Greedy | Sorting | MaxHeapComprehensive Code · 15:18 · 1K views
502. IPO - JAVA (Two heaps - Detailed explanation + coding)Sourin Majumdar · 13:43 · 1K views
IPO - Leetcode 502 - PythonTechError · 10:56 · 912 views
Leetcode Hard 502 IPO Conceptually Important Coding QuestionAlgorithmicIQ · 38:36 · 842 views
The problem
Suppose LeetCode will start its IPO soon. In order to sell a good price of its shares to Venture Capital, LeetCode would like to work on some projects to increase its capital before the IPO. Since it has limited resources, it can only finish at most k distinct projects before the IPO. Help LeetCode design the best way to maximize its total capital after finishing at most k distinct projects.
You are given n projects where the ith project has a pure profit profits[i] and a minimum capital of capital[i] is needed to start it.
Initially, you have w capital. When you finish a project, you will obtain its pure profit and the profit will be added to your total capital.
Pick a list of at most k distinct projects from given projects to maximize your final capital, and return the final maximized capital.
The answer is guaranteed to fit in a 32-bit signed integer.
Example 1:
Input: k = 2, w = 0, profits = [1,2,3], capital = [0,1,1] Output: 4 Explanation: Since your initial capital is 0, you can only start the project indexed 0. After finishing it you will obtain profit 1 and your capital becomes 1. With capital 1, you can either start the project indexed 1 or the project indexed 2. Since you can choose at most 2 projects, you need to finish the project indexed 2 to get the maximum capital. Therefore, output the final maximized capital, which is 0 + 1 + 3 = 4.
Example 2:
Input: k = 3, w = 0, profits = [1,2,3], capital = [0,1,2] Output: 6
Constraints:
1 <= k <= 1050 <= w <= 109n == profits.lengthn == capital.length1 <= n <= 1050 <= profits[i] <= 1040 <= capital[i] <= 109
Approach overview
Problem Overview: You start with initial capital w and can complete at most k projects. Each project requires a minimum capital and yields a profit. After finishing a project, the profit is added to your capital. The goal is to choose projects that maximize your final capital.
Approach 1: Dynamic Programming with Memoization (Exponential → O(n * k) states, higher overhead)
This approach models the decision process: at each step, choose a project that can be afforded with the current capital or skip it. Recursion explores combinations of up to k projects while memoization stores intermediate results to avoid recomputation. Although this captures the problem structure clearly, the state depends on remaining projects and capital values, which can grow large. Time complexity is roughly O(n * k) states but with expensive transitions, and space complexity is O(n * k) for the memo table and recursion stack. Useful for understanding the decision space but rarely practical for large inputs.
Approach 2: Greedy with Priority Queue (Sorting + Max Heap) (O(n log n + k log n))
The key observation: whenever multiple projects are affordable, choosing the one with the highest profit always increases future options. Start by pairing each project's capital requirement with its profit and sort them by required capital. Iterate through the list and push all projects whose capital requirement is ≤ current capital into a max heap. This heap stores profits so you can always pick the most profitable available project.
Repeat the process for at most k selections: push newly affordable projects into the heap, then extract the maximum profit and add it to your capital. The heap operations come from a priority queue, while the strategy itself is a classic greedy choice. Sorting costs O(n log n), and each of the at most k heap operations costs O(log n). Space complexity is O(n) for the heap and sorted project list.
Recommended for interviews: The greedy heap solution is the expected answer. Interviewers want to see that you recognize the pattern: sort by capital, push feasible projects into a max heap, and always select the highest profit. Mentioning the dynamic programming interpretation shows problem understanding, but implementing the greedy + priority queue approach demonstrates practical algorithm design skills.
Approach 1: Greedy Approach with Priority Queue
This approach uses a greedy algorithm in conjunction with priority queues to select the projects maximizing profits. The core idea is to iteratively choose the most profitable project that can be initiated with the current available capital.
Initially, projects are stored in a list along with their capital and profit requirements. We prioritize by try fetching the most profitable projects. We maintain available projects in a max-heap (or priority queue) to achieve this efficiently.
We attempt to start at most k projects, updating the available capital every time we include a new project. For every iteration, once we identify the most feasible projects that can be executed given the current capital, we pick and execute the one with the highest profit and update our available capital. This process repeats until no more projects can be started or we've reached k projects.
import heapq
class Solution:
def findMaximizedCapital(self, k, w, Profits, Capital):
n = len(Profits)
projects = list(zip(Capital, Profits))
projects.sort()
max_profit_heap = []
index = 0
for _ in range(k):
while index < n and projects[index][0] <= w:
heapq.heappush(max_profit_heap, -projects[index][1])
index += 1
if not max_profit_heap:
break
w -= heapq.heappop(max_profit_heap)
return w
if __name__ == '__main__':
k = 2
w = 0
Profits = [1, 2, 3]
Capital = [0, 1, 1]
sol = Solution()
result = sol.findMaximizedCapital(k, w, Profits, Capital)
print(f"The final maximized capital is: {result}")import java.util.PriorityQueue;
import java.util.Arrays;
class Solution {
public int findMaximizedCapital(int k, int w, int[] Profits, int[] Capital) {
int n = Profits.length;
Project[] projects = new Project[n];
for (int i = 0; i < n; i++) {
projects[i] = new Project(Profits[i], Capital[i]);
}
Arrays.sort(projects, (a, b) -> a.capital - b.capital);
PriorityQueue<Integer> maxProfitHeap = new PriorityQueue<>( (a, b) -> b - a );
int index = 0;
while (k-- > 0) {
while (index < n && projects[index].capital <= w) {
maxProfitHeap.add(projects[index++].profit);
}
if (maxProfitHeap.isEmpty()) break;
w += maxProfitHeap.poll();
}
return w;
}
static class Project {
int profit;
int capital;
Project(int profit, int capital) {
this.profit = profit;
this.capital = capital;
}
}
public static void main(String[] args) {
Solution sol = new Solution();
int k = 2;
int w = 0;
int[] Profits = {1, 2, 3};
int[] Capital = {0, 1, 1};
int result = sol.findMaximizedCapital(k, w, Profits, Capital);
System.out.println("The final maximized capital is: " + result);
}
}#include <iostream>
#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;
for (int i = 0; i < Profits.size(); ++i) {
projects.push_back({Capital[i], Profits[i]});
}
sort(projects.begin(), projects.end());
priority_queue<int> maxProfitHeap;
int curr = 0;
while (k--) {
while (curr < projects.size() && projects[curr].first <= w) {
maxProfitHeap.push(projects[curr++].second);
}
if (maxProfitHeap.empty()) break;
w += maxProfitHeap.top();
maxProfitHeap.pop();
}
return w;
}
};
int main() {
int k = 2, w = 0;
vector<int> Profits{1, 2, 3};
vector<int> Capital{0, 1, 1};
Solution sol;
int result = sol.findMaximizedCapital(k, w, Profits, Capital);
cout << "The final maximized capital is: " << result << endl;
return 0;
}class Project {
constructor(capital, profit) {
this.capital = capital;
this.profit = profit;
}
}
var findMaximizedCapital = function(k, w, Profits, Capital) {
let projects = [];
for (let i = 0; i < Profits.length; ++i) {
projects.push(new Project(Capital[i], Profits[i]));
}
projects.sort((a, b) => a.capital - b.capital);
let maxProfitHeap = new Heap((a, b) => b.profit - a.profit);
let curr = 0;
while (k-- > 0) {
while (curr < projects.length && projects[curr].capital <= w) {
maxProfitHeap.push(projects[curr++]);
}
if (maxProfitHeap.isEmpty()) break;
w += maxProfitHeap.pop().profit;
}
return w;
};
// An example max-heap implementation in JS
class Heap {
constructor(compare) {
this.compare = compare;
this.data = [];
}
push(item) {
this.data.push(item);
this.bubbleUp(this.data.length - 1);
}
pop() {
if (this.data.length === 1) return this.data.pop();
const item = this.data[0];
this.data[0] = this.data.pop();
this.bubbleDown(0);
return item;
}
isEmpty() {
return this.data.length === 0;
}
bubbleUp(index) {
while (index > 0) {
const parentIndex = (index - 1) >> 1;
if (this.compare(this.data[index], this.data[parentIndex]) <= 0) break;
[this.data[parentIndex], this.data[index]] = [this.data[index], this.data[parentIndex]];
index = parentIndex;
}
}
bubbleDown(index) {
const length = this.data.length;
while (true) {
const left = (index << 1) + 1;
const right = left + 1;
let largest = index;
if (left < length && this.compare(this.data[left], this.data[largest]) > 0) {
largest = left;
}
if (right < length && this.compare(this.data[right], this.data[largest]) > 0) {
largest = right;
}
if (largest === index) break;
[this.data[index], this.data[largest]] = [this.data[largest], this.data[index]];
index = largest;
}
}
}
// Example Usage
console.log("The final maximized capital is: " + findMaximizedCapital(2, 0, [1, 2, 3], [0, 1, 1]));#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
struct Project {
int capital;
int profit;
};
int cmpCapital(const void* a, const void* b) {
return ((struct Project*)a)->capital - ((struct Project*)b)->capital;
}
int cmpMaxProfitDesc(const void* a, const void* b) {
return ((*(int**)b)[1]) - ((*(int**)a)[1]);
}
int findMaximizedCapital(int k, int w, int* profits, int* capital, int n) {
struct Project* projects = (struct Project*)malloc(n * sizeof(struct Project));
for (int i = 0; i < n; ++i) {
projects[i].capital = capital[i];
projects[i].profit = profits[i];
}
qsort(projects, n, sizeof(struct Project), cmpCapital);
int** maxProfitHeap = (int**)malloc(n * sizeof(int*));
int maxProfitHeapSize = 0;
int currentCapital = w;
int index = 0;
while (k-- > 0) {
while (index < n && projects[index].capital <= currentCapital) {
int* elem = (int*)malloc(2 * sizeof(int));
elem[0] = projects[index].capital;
elem[1] = projects[index].profit;
maxProfitHeap[maxProfitHeapSize++] = elem;
index++;
}
if (maxProfitHeapSize == 0) break;
qsort(maxProfitHeap, maxProfitHeapSize, sizeof(int*), cmpMaxProfitDesc);
currentCapital += maxProfitHeap[0][1];
free(maxProfitHeap[0]);
for (int i = 1; i < maxProfitHeapSize; ++i) {
maxProfitHeap[i - 1] = maxProfitHeap[i];
}
maxProfitHeapSize--;
}
free(projects);
free(maxProfitHeap);
return currentCapital;
}
int main() {
int k = 2;
int w = 0;
int profits[] = {1, 2, 3};
int capital[] = {0, 1, 1};
int n = sizeof(profits) / sizeof(profits[0]);
int result = findMaximizedCapital(k, w, profits, capital, n);
printf("The final maximized capital is: %d\n", result);
return 0;
}using System;
using System.Collections.Generic;
class Solution
{
public int FindMaximizedCapital(int k, int w, int[] profits, int[] capital)
{
int n = profits.Length;
(int capital, int profit)[] projects = new (int, int)[n];
for (int i = 0; i < n; ++i)
{
projects[i] = (capital[i], profits[i]);
}
Array.Sort(projects, (a, b) => a.capital - b.capital);
var maxProfitHeap = new SortedSet<(int profit, int index)>();
int curr = 0;
while (k-- > 0)
{
while (curr < n && projects[curr].capital <= w)
{
maxProfitHeap.Add((projects[curr].profit, curr));
++curr;
}
if (maxProfitHeap.Count == 0) break;
var maxProfit = new List<(int profit, int index)>(maxProfitHeap);
var maxVal = maxProfit[^1];
w += maxVal.profit;
maxProfitHeap.Remove(maxVal);
}
return w;
}
}
class Program
{
static void Main(string[] args)
{
Solution sol = new Solution();
int k = 2, w = 0;
int[] profits = {1, 2, 3};
int[] capital = {0, 1, 1};
int result = sol.FindMaximizedCapital(k, w, profits, capital);
Console.WriteLine($"The final maximized capital is: {result}");
}
}Python's solution takes advantage of its heapq module to function as a max-heap by negating values. It begins by sorting merged project lists on capital, allowing for easy discovery of executable projects by the current capital. Feasible projects are added to a max heap where the most profitable one is repeatedly accessed, increasing capital available for the next iteration.
Time Complexity: O(N log N + K log N) — due to sorting and repeated heap operations in each loop cycle.
Space Complexity: O(N) — primarily from up situational storage for projects and heaps.
Approach 2: Dynamic Programming with Memoization
This dynamic programming approach delineates with the constraints arising from the number of projects executable. We maintain an accruing structure documenting the maximum possible capital achievable for the number of projects done. The dynamic programming strategy opts for inherent flexibility where feasible, owing to recursive checks across all projects capable of initiating.
Memoization ensures repeated subproblem results are accessed without re-computation, improving overall efficiency particularly in scenarios involving expedient overlap of capital ranges per project.
Despite the computationally exhaustive exploration of all project combinations, dynamic memorized caching dramatically curtails avoidant efforts during feasibility analysis—gaining crucial strategic leverage with convenience in considering bounded project completion restrictions.
def findMaximizedCapital(k, w, profits, capital):
n = len(profits)
projects = list(zip(capital, profits))
projects.sort()
dp = [[-1] * (k + 1) for _ in range(n)]
def maximum(k, w, idx):
if k == 0 or idx == n:
return w
if dp[idx][k] != -1:
return dp[idx][k]
notTake = maximum(k, w, idx + 1)
take = 0
if w >= projects[idx][0]:
take = maximum(k - 1, w + projects[idx][1], idx + 1)
dp[idx][k] = max(take, notTake)
return dp[idx][k]
return maximum(k, w, 0)
if __name__ == '__main__':
k = 2
w = 0
profits = [1, 2, 3]
capital = [0, 1, 1]
result = findMaximizedCapital(k, w, profits, capital)
print(f"The final maximized capital is: {result}")import java.util.Arrays;
import java.util.Comparator;
public class Solution {
Integer[][] dp;
private int findMaximum(int k, int w, int[][] projects, int idx) {
if (k == 0 || idx >= projects.length) return w;
if (dp[idx][k] != null) return dp[idx][k];
int notTake = findMaximum(k, w, projects, idx + 1);
int take = 0;
if (w >= projects[idx][0]) {
take = findMaximum(k - 1, w + projects[idx][1], projects, idx + 1);
}
return dp[idx][k] = Math.max(take, notTake);
}
public int findMaximizedCapital(int k, int w, int[] profits, int[] capital) {
int[][] projects = new int[profits.length][2];
for (int i = 0; i < profits.length; i++) {
projects[i][0] = capital[i];
projects[i][1] = profits[i];
}
dp = new Integer[profits.length][k + 1];
return findMaximum(k, w, projects, 0);
}
public static void main(String[] args) {
Solution sol = new Solution();
int k = 2, w = 0;
int[] profits = {1, 2, 3};
int[] capital = {0, 1, 1};
int result = sol.findMaximizedCapital(k, w, profits, capital);
System.out.println("The final maximized capital is: " + result);
}
}#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
private:
vector<vector<int>> dp;
int findMaximum(int k, int w, vector<pair<int, int>>& projects, int idx) {
if (k == 0 || idx >= projects.size())
return w;
if (dp[idx][k] != -1)
return dp[idx][k];
int notTake = findMaximum(k, w, projects, idx + 1);
int take = 0;
if (w >= projects[idx].first)
take = findMaximum(k - 1, w + projects[idx].second, projects, idx + 1);
return dp[idx][k] = max(take, notTake);
}
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]});
}
dp.assign(n, vector<int>(k + 1, -1));
return findMaximum(k, w, projects, 0);
}
};
int main() {
Solution sol;
int k = 2;
int w = 0;
vector<int> profits = {1, 2, 3};
vector<int> capital = {0, 1, 1};
int result = sol.findMaximizedCapital(k, w, profits, capital);
cout << "The final maximized capital is: " << result << endl;
return 0;
}var findMaximizedCapital = function(k, w, Profits, Capital) {
const n = Profits.length;
const projects = Capital.map((cap, i) => ({capital: cap, profit: Profits[i]}));
projects.sort((a, b) => a.capital - b.capital);
const dp = Array.from({length: n}, () => Array(k + 1).fill(null));
const maximum = (k, w, idx) => {
if (k === 0 || idx === n) return w;
if (dp[idx][k] !== null) return dp[idx][k];
const notTake = maximum(k, w, idx + 1);
let take = 0;
if (w >= projects[idx].capital) {
take = maximum(k - 1, w + projects[idx].profit, idx + 1);
}
dp[idx][k] = Math.max(take, notTake);
return dp[idx][k];
};
return maximum(k, w, 0);
};
console.log("The final maximized capital is: " + findMaximizedCapital(2, 0, [1, 2, 3], [0, 1, 1]));#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
int max(int a, int b) {
return a > b ? a : b;
}
int dp(int k, int w, int* profits, int* capital, int n, int current, int cache[][1001]) {
if (k == 0 || current == n) {
return w;
}
if (cache[current][w] != -1) {
return cache[current][w];
}
int notTake = dp(k, w, profits, capital, n, current + 1, cache);
int take = w >= capital[current] ? dp(k - 1, w + profits[current], profits, capital, n, current + 1, cache) : 0;
return cache[current][w] = max(take, notTake);
}
int findMaximizedCapital(int k, int w, int* profits, int* capital, int n) {
int cache[1000][1001];
for (int i = 0; i < 1000; ++i) {
for (int j = 0; j < 1001; ++j) {
cache[i][j] = -1;
}
}
return dp(k, w, profits, capital, n, 0, cache);
}
int main() {
int k = 2;
int w = 0;
int profits[] = {1, 2, 3};
int capital[] = {0, 1, 1};
int n = sizeof(profits) / sizeof(profits[0]);
int result = findMaximizedCapital(k, w, profits, capital, n);
printf("The final maximized capital is: %d\n", result);
return 0;
}using System;
using System.Collections.Generic;
using System.Linq;
class Solution {
private int[,] dp;
private int Maximum(int k, int w, Tuple<int, int>[] projects, int idx) {
if (k == 0 || idx >= projects.Length)
return w;
if (dp[idx, k] != -1)
return dp[idx, k];
int notTake = Maximum(k, w, projects, idx + 1);
int take = 0;
if (w >= projects[idx].Item1)
take = Maximum(k - 1, w + projects[idx].Item2, projects, idx + 1);
dp[idx, k] = Math.Max(take, notTake);
return dp[idx, k];
}
public int FindMaximizedCapital(int k, int w, int[] profits, int[] capital) {
var projects = new Tuple<int, int>[profits.Length];
for (int i = 0; i < profits.Length; i++)
projects[i] = new Tuple<int, int>(capital[i], profits[i]);
dp = new int[profits.Length, k + 1];
for (int i = 0; i < profits.Length; ++i)
for (int j = 0; j <= k; ++j)
dp[i, j] = -1;
return Maximum(k, w, projects, 0);
}
}
class Program {
static void Main(string[] args) {
Solution sol = new Solution();
int k = 2;
int w = 0;
int[] profits = {1, 2, 3};
int[] capital = {0, 1, 1};
int result = sol.FindMaximizedCapital(k, w, profits, capital);
Console.WriteLine("The final maximized capital is: " + result);
}
}Python implementation continues memorized logic in a recursive scheme, capturing potential resultant savings per states considering active capital and project tendency.
Recursion navigation ensures attempt pathways build upon pre-completed operations enumerated regarding projects long-lost and those advancing given adjacent states.
Time Complexity: O(N * W * K) — large-scale evaluation across potential dimensions concerning k-th invoked reliance on available projects.
Space Complexity: O(N * K) — mapped towards computed values storyboarding across projects tallying any k factorial bounds.
Approach 3: Default Approach
func findMaximizedCapital(k int, w int, profits []int, capital []int) int {
q1 := hp2{}
for i, c := range capital {
heap.Push(&q1, pair{c, profits[i]})
}
q2 := hp{}
for k > 0 {
for len(q1) > 0 && q1[0].c <= w {
heap.Push(&q2, heap.Pop(&q1).(pair).p)
}
if q2.Len() == 0 {
break
}
w += heap.Pop(&q2).(int)
k--
}
return w
}
type hp struct{ sort.IntSlice }
func (h hp) Less(i, j int) bool { return h.IntSlice[i] > h.IntSlice[j] }
func (h *hp) Push(v any) { h.IntSlice = append(h.IntSlice, v.(int)) }
func (h *hp) Pop() any {
a := h.IntSlice
v := a[len(a)-1]
h.IntSlice = a[:len(a)-1]
return v
}
type pair struct{ c, p int }
type hp2 []pair
func (h hp2) Len() int { return len(h) }
func (h hp2) Less(i, j int) bool { return h[i].c < h[j].c }
func (h hp2) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *hp2) Push(v any) { *h = append(*h, v.(pair)) }
func (h *hp2) Pop() any { a := *h; v := a[len(a)-1]; *h = a[:len(a)-1]; return v }Complexity comparison
| Approach | Complexity |
|---|---|
| Greedy Approach with Priority Queue | Time Complexity: O(N log N + K log N) — due to sorting and repeated heap operations in each loop cycle. Space Complexity: O(N) — primarily from up situational storage for projects and heaps. |
| Dynamic Programming with Memoization | Time Complexity: O(N * W * K) — large-scale evaluation across potential dimensions concerning k-th invoked reliance on available projects. Space Complexity: O(N * K) — mapped towards computed values storyboarding across projects tallying any k factorial bounds. |
| Default Approach | – |
Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Dynamic Programming with Memoization | O(n * k) states (high transition cost) | O(n * k) | Conceptual exploration of decision space or small constraints |
| Greedy with Sorting + Max Heap | O(n log n + k log n) | O(n) | Optimal solution for large inputs and expected interview answer |
Frequently asked questions
Is IPO easy or hard?
IPO is classified as a Hard problem because it combines multiple concepts: greedy selection, sorting by constraints, and efficient retrieval using a priority queue. The trick is recognizing that choosing the highest profit among currently affordable projects leads to the optimal result.
IPO Python/Java solution
Python solutions typically use heapq with negative values to simulate a max heap, while Java implementations use PriorityQueue with a custom comparator. Both follow the same pattern: sort projects by capital, push feasible profits into the heap, and select the maximum profit up to k times.
How to solve IPO in O(n)?
A strict O(n) solution is generally not feasible because selecting the maximum profit among available projects requires priority ordering. The standard optimal approach sorts projects and uses a max heap, resulting in O(n log n + k log n) time complexity.
What is the best approach for IPO?
The optimal approach uses a greedy strategy with sorting and a max heap (priority queue). First sort projects by required capital, then push all affordable projects into a max heap of profits. Each iteration selects the highest profit project and adds its profit to the current capital. This runs in O(n log n + k log n) time.
Is IPO asked at Google/Amazon/Meta?
IPO is a common hard-level interview problem that tests greedy strategy and priority queue usage. Variants of this pattern appear in interviews at large tech companies such as Amazon, Google, and Meta, especially when evaluating heap-based optimization problems.
What data structure is used in IPO?
The core data structure is a max heap (priority queue) that stores profits of currently affordable projects. Sorting is also used to process projects in increasing order of required capital. Together they enable efficient greedy selection of the most profitable project.
What is the time complexity of IPO?
The optimal greedy solution runs in O(n log n + k log n) time. Sorting projects by required capital takes O(n log n), and each of the at most k project selections involves a heap push or pop costing O(log n). Space complexity is O(n) for the heap and project list.
