← ~/lc-grind/posts

#502 - IPO

December 26, 2025

IPO

Problem Description

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 <= 105
  • 0 <= w <= 109
  • n == profits.length
  • n == capital.length
  • 1 <= n <= 105
  • 0 <= profits[i] <= 104
  • 0 <= capital[i] <= 109

Solution

1. Problem Deconstruction (500+ words)

Rewrite the Problem

  1. Technical Version:
    Given an integer k (maximum distinct projects), integer w (initial capital), and arrays profits and capital of length n, where project i requires capital capital[i] to start and yields pure profit profits[i] upon completion. Starting with capital w, we may select a sequence of at most k distinct projects. A project can only be started if current capital ≥ its required capital. After completion, the profit is added to the capital. The goal is to choose a sequence (order matters due to capital constraints) maximizing final capital. Return the maximized capital.

  2. Beginner Version:
    You start with some money w. There are n projects, each needing a certain investment (capital) to start and giving a profit when finished. You can do at most k projects (each only once). You can only start a project if you have enough money to cover its investment. After finishing, you get the profit added to your money. Choose which projects to do and in what order to end up with the most money.

  3. Mathematical Version:
    Let ( P = {(c_i, p_i)}{i=1}^n ) where ( c_i ) = capital required, ( p_i ) = profit. Initially, ( C_0 = w ). Find a sequence of distinct indices ( i_1, i_2, \dots, i_m ) with ( m \le k ) such that for each ( t ), ( C{t-1} \ge c_{i_t} ) and ( C_t = C_{t-1} + p_{i_t} ). Maximize ( C_m ).
    Equivalently, define the feasible set at step ( t ) as ( { i : c_i \le C_{t-1}, i \notin \text{taken} } ). Choose ( i_t = \arg\max_{i \in \text{feasible}} p_i ), update ( C_t ), and repeat up to ( k ) times.

Constraint Analysis

  • 1 <= k <= 1e5: Limits number of iterations; algorithm must be better than ( O(k \cdot n) ).
  • 0 <= w <= 1e9: Initial capital can be huge, but profits are bounded (≤1e4), so final capital fits in 32-bit signed integer (≤2e9).
  • n == profits.length == capital.length, 1 <= n <= 1e5: Large input size; ( O(n^2) ) impossible, need ( O(n \log n) ) or similar.
  • 0 <= profits[i] <= 1e4: Profits are non‑negative; zero‑profit projects exist but are suboptimal unless no positive‑profit projects are affordable.
  • 0 <= capital[i] <= 1e9: Capital requirements can exceed initial capital; we may not afford any project initially.

Hidden Edge Cases:

  • k > n: We can only do at most n projects.
  • All capital[i] > w initially: no project can be started; return w.
  • Projects with capital[i] = 0 are always affordable, even if w = 0.
  • Multiple projects with same capital but different profits: must consider all when affordable.
  • Greedy approach must dynamically update the set of affordable projects as capital grows.

2. Intuition Scaffolding

Generate 3 Analogies

  1. Real‑World Metaphor:
    Imagine you are a venture capitalist with initial fund w. Each project is a startup requiring an investment (capital) and promising a return (profit). You have bandwidth for at most k investments. After each investment, your fund grows, enabling larger investments. The optimal strategy is to always invest in the most profitable affordable startup to grow your fund fastest.

  2. Gaming Analogy:
    In a resource‑management game, you start with w gold. You have k turns. Each turn, you can buy a building that costs c_i gold and yields p_i gold immediately. You can only buy if you have enough gold. The goal is to maximize gold after k turns. The best tactic: each turn, buy the building with the highest profit among those you can afford.

  3. Math Analogy:
    This is a matroid‑like selection problem: projects are independent except for the capital constraint. The feasible set at any time is an independence set defined by a threshold. The profit function is additive, so a greedy algorithm that picks the maximum profit element from the feasible set at each step yields the global optimum (by exchange argument).

Common Pitfalls Section

  1. Picking highest profit ignoring capital: May choose an unaffordable project, stalling progress.
  2. Picking lowest capital first: Might waste a slot on a low‑profit project, missing later high‑profit opportunities.
  3. Dynamic programming over capital: Capital can reach 1e9, making DP table impossible.
  4. Brute‑force over subsets: ( \binom{n}{k} ) is astronomical for large n.
  5. Assuming order doesn’t matter: Actually, doing a low‑profit project first may delay affording a high‑profit one, reducing final capital.
  6. Overlooking zero‑profit projects: They don’t increase capital but consume k; should be avoided unless necessary.
  7. Forgetting that k may exceed n: Must limit iterations to available projects.

3. Approach Encyclopedia

Brute Force

  • Enumerate all sequences of length ≤ k from n projects.
  • For each sequence, check feasibility and compute final capital.
  • Complexity: ( O(\sum_{i=1}^k P(n, i)) ), infeasible for n up to 1e5.

Dynamic Programming (Infeasible)

  • State: (projects selected, current capital) – state space too large.
  • Capital can grow up to 2e9, cannot index by capital.

Greedy with Two Heaps (Optimal)

  • Intuition: At each step, among affordable projects (capital ≤ current capital), pick the highest profit. After picking, capital increases, potentially making new projects affordable.
  • Data Structures:
    • Sort projects by required capital.
    • Use a max‑heap to store profits of currently affordable projects.
    • Maintain a pointer to iterate through sorted projects as capital grows.
  • Algorithm:
    1. Sort projects by capital ascending.
    2. Initialize max‑heap (empty), pointer i = 0, current capital = w.
    3. For up to k iterations:
      • While i < n and projects[i].capital ≤ current capital:
        • Push projects[i].profit into max‑heap.
        • i++.
      • If heap empty: break.
      • Pop max profit from heap, add to current capital.
    4. Return current capital.

Pseudocode

function findMaximizedCapital(k, w, profits, capital):
    n = length(profits)
    projects = list of (capital[i], profits[i]) for i in [0..n-1]
    sort projects by capital ascending
    maxHeap = empty max-heap (use min-heap with negatives)
    i = 0
    for iteration in 1..k:
        while i < n and projects[i].capital <= w:
            push -projects[i].profit onto maxHeap  # store negative for max-heap
            i += 1
        if maxHeap is empty:
            break
        w += -pop from maxHeap  # add the positive profit
    return w

Complexity Proof

  • Sorting: ( O(n \log n) ).
  • Each project pushed once, popped at most once: ( O(n \log n) ) for heap operations.
  • k iterations, each pop: ( O(k \log n) ).
  • Total: ( O((n + k) \log n) ). Since ( k ≤ 10^5, n ≤ 10^5 ), this is efficient.
  • Space: ( O(n) ) for projects and heap.

Visualization
Example 1: k=2, w=0, profits=[1,2,3], capital=[0,1,1]
Sorted projects: [(0,1), (1,2), (1,3)]
Step 1: capital=0 → add (0,1) → heap {1} → pop 1 → capital=1
Step 2: capital=1 → add (1,2),(1,3) → heap {2,3} → pop 3 → capital=4
Result: 4.


4. Code Deep Dive

import heapq
from typing import List

class Solution:
    def findMaximizedCapital(self, k: int, w: int, profits: List[int], capital: List[int]) -> int:
        # Pair each project's capital requirement with its profit
        projects = list(zip(capital, profits))
        # Sort projects by capital requirement (ascending)
        projects.sort(key=lambda x: x[0])
        
        # Max-heap simulated via min-heap with negative profits
        max_heap = []
        n = len(projects)
        i = 0  # pointer to the next project to consider
        
        for _ in range(k):
            # Add all projects that become affordable with current capital w
            while i < n and projects[i][0] <= w:
                # Push negative profit to make min-heap act as max-heap
                heapq.heappush(max_heap, -projects[i][1])
                i += 1
            
            # If no affordable projects, stop early
            if not max_heap:
                break
            
            # Take the project with the highest profit
            w += -heapq.heappop(max_heap)
        
        return w

Line‑by‑Line Annotations

  1. projects = list(zip(capital, profits)): Creates tuples (capital, profit) for each project.
  2. projects.sort(key=lambda x: x[0]): Sorts by capital requirement so we can efficiently find affordable projects as capital increases.
  3. max_heap = []: Initializes a min‑heap (will store negative profits).
  4. n = len(projects), i = 0: i tracks how many projects have been added to the heap.
  5. for _ in range(k):: Attempt up to k projects.
  6. while i < n and projects[i][0] <= w:: Adds all newly affordable projects to the heap.
    • heapq.heappush(max_heap, -projects[i][1]): Stores negative profit (so smallest in heap = highest profit).
    • i += 1: Moves pointer forward.
  7. if not max_heap: break: If no affordable projects, stop.
  8. w += -heapq.heappop(max_heap): Pops the most negative (highest profit), adds to capital.
  9. return w: Final maximized capital.

Edge Case Handling

  • Example 2: k=3, w=0, profits=[1,2,3], capital=[0,1,2] → returns 6 (as described).
  • All capital > w: While loop adds nothing, heap empty, returns w.
  • Zero profits: Negative of zero is zero; higher positive profits (negative values) are popped first, so zero‑profit projects are only taken if no positive ones exist.
  • k > n: Loop runs at most k times, but breaks when heap empty (after n projects).

5. Complexity War Room

Hardware‑Aware Analysis

  • For n = 10^5, sorting uses ~1.7 M comparisons (quick sort), fast.
  • Heap operations: ~2 × 10^5 pushes/pops, each O(log n) ≈ 17 steps → ~3.4 M operations, well within CPU limits.
  • Memory: projects list ~1.6 MB (8 bytes per int × 2 × 10^5), heap similar, fits in L3 cache (typical 8–16 MB).

Industry Comparison Table

Approach Time Complexity Space Complexity Readability Interview Viability
Brute Force O(n^k) O(k) 9/10 ❌ Fails large n
DP over capital O(w + n) O(w) 5/10 ❌ w up to 1e9
Greedy + Two Heaps O((n+k) log n) O(n) 8/10 ✅ Optimal & efficient

6. Pro Mode Extras

Variants Section

  1. Projects with fees: If each project has a fee f_i deducted after completion, net profit = profits[i] - f_i. Greedy still works if we use net profit.
  2. Repeatable projects: If projects can be repeated, after each completion, re‑add it to the candidate set (if capital requirement still met). Greedy may still work but must avoid infinite loops with zero profit.
  3. Time‑dependent profits: If profit decreases over time, becomes a scheduling problem (like weighted interval scheduling).
  4. Multiple resources: If projects require multiple resources (e.g., capital and labor), becomes multi‑dimensional knapsack.
  5. LeetCode 123 (2 transactions): Different but related; that problem uses DP with states for transactions left and holding stock.

Interview Cheat Sheet

  • Clarify: Can projects be done in any order? Yes, as long as capital requirement met.
  • Brute force first: Acknowledge exponential complexity, then optimize.
  • Key insight: Greedy on highest profit among affordable projects.
  • Data structures: Sorting + max‑heap.
  • Complexity: ( O((n+k)\log n) ) time, ( O(n) ) space.
  • Edge cases: k > n, zero profits, all projects unaffordable initially.
  • Coding tip: Use negative profits for max‑heap in Python.
  • Proof sketch: Exchange argument – swapping any project in optimal solution with a higher‑profit affordable one cannot reduce total profit.