502. IPO - Explanation

Problem Link

Description

A company has limited resources, it can only finish at most k distinct projects before the IPO. Help the company to 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 = 3, w = 0, profits = [1,4,2,3], capital = [0,3,1,1]

Output: 8

Explanation : The order of indices to pick are [0,3,1] and final capital is (1 + 3 + 4) = 8.

Example 2:

Input: k = 4, w = 2, profit = [2,3,1,5,3], capital = [4,4,2,3,3]

Output: 14

Explanation: The order of indices to pick are [2,3,4,1] and final capital is (2 + (1 + 5 + 3 + 3)) = 14.

Constraints:

  • n == profits.length == capital.length
  • 1 <= n, k <= 100,000
  • 0 <= w <= 1,000,000,000
  • 0 <= profits[i] <= 10,000
  • 0 <= capital[i] <= 1,000,000,000


Topics

Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Heap / Priority Queue - Understanding min-heaps and max-heaps for efficient extraction of minimum and maximum elements
  • Greedy Algorithms - Recognizing when selecting the locally optimal choice leads to a globally optimal solution
  • Sorting - Sorting data to process elements in a specific order

1. Two Heaps

Intuition

To maximize capital after completing at most k projects, we should always pick the project with the highest profit among those we can currently afford. A min-heap ordered by capital lets us efficiently find all affordable projects as our capital grows. A max-heap ordered by profit then lets us pick the most profitable one. After completing a project, our capital increases, potentially unlocking more projects from the min-heap to consider.

Algorithm

  1. Build a min-heap of all projects ordered by their capital requirement.
  2. Initialize a max-heap for profits (empty at start).
  3. Repeat up to k times:
    • Move all projects from the min-heap whose capital requirement is at most w into the max-heap.
    • If the max-heap is empty, no more projects can be started, so break.
    • Pop the top of the max-heap (highest profit) and add it to w.
  4. Return the final capital w.
class Solution:
    def findMaximizedCapital(self, k: int, w: int, profits: List[int], capital: List[int]) -> int:
        maxProfit = []  # Max heap
        minCapital = [(c, p) for c, p in zip(capital, profits)]  # Min heap
        heapq.heapify(minCapital)

        for _ in range(k):
            while minCapital and minCapital[0][0] <= w:
                c, p = heapq.heappop(minCapital)
                heapq.heappush(maxProfit, -p)

            if not maxProfit:
                break

            w += -heapq.heappop(maxProfit)

        return w

Time & Space Complexity

  • Time complexity: O(nlogn)O(n \log n)
  • Space complexity: O(n)O(n)

2. Two Heaps (Optimal)

Intuition

This approach improves on the previous one by storing only indices in the heaps rather than copying the actual capital and profit values. The heaps use custom comparators that reference the original arrays. This reduces memory overhead while maintaining the same greedy strategy: always select the most profitable project among those currently affordable.

Algorithm

  1. Build a min-heap of project indices, ordered by capital[index].
  2. Initialize an empty max-heap of indices, to be ordered by profits[index].
  3. Repeat up to k times:
    • Transfer all indices from the min-heap where capital[index] <= w into the max-heap.
    • If the max-heap is empty, break.
    • Pop the index with maximum profit, add profits[index] to w.
  4. Return w.
class Solution:
    def findMaximizedCapital(self, k: int, w: int, profits: List[int], capital: List[int]) -> int:
        class Node:
            def __init__(self, idx):
                self.idx = idx

            def __lt__(self, other):
                if capital[self.idx] != capital[other.idx]:
                    return capital[self.idx] < capital[other.idx]
                return self.idx < other.idx

        minCapital = []
        maxProfit = []
        for i in range(len(capital)):
            heapq.heappush(minCapital, Node(i))

        for _ in range(k):
            while minCapital and capital[minCapital[0].idx] <= w:
                idx = heapq.heappop(minCapital).idx
                heapq.heappush(maxProfit, -profits[idx])

            if not maxProfit:
                break
            w += -heapq.heappop(maxProfit)

        return w

Time & Space Complexity

  • Time complexity: O(nlogn)O(n \log n)
  • Space complexity: O(n)O(n)

3. Sorting + Max-Heap

Intuition

Instead of using a min-heap for capital, we can simply sort the projects by their capital requirements upfront. A pointer then tracks which projects have become affordable. As capital grows, we advance this pointer and push newly affordable project profits into a max-heap. This avoids repeated heap operations on the capital side and is often faster in practice due to better cache performance from the sorted array traversal.

Algorithm

  1. Create an array of indices [0, 1, ..., n-1] and sort it by capital[index].
  2. Initialize a max-heap for profits and a pointer idx = 0.
  3. Repeat up to k times:
    • While idx < n and capital[indices[idx]] <= w, push profits[indices[idx]] onto the max-heap and increment idx.
    • If the max-heap is empty, break.
    • Pop the maximum profit and add it to w.
  4. Return w.
class Solution:
    def findMaximizedCapital(self, k: int, w: int, profits: List[int], capital: List[int]) -> int:
        n = len(profits)
        indices = list(range(n))
        indices.sort(key=lambda i: capital[i])

        maxProfit, idx = [], 0
        for _ in range(k):
            while idx < n and capital[indices[idx]] <= w:
                heapq.heappush(maxProfit, -profits[indices[idx]])
                idx += 1

            if not maxProfit:
                break
            w += -heapq.heappop(maxProfit)

        return w

Time & Space Complexity

  • Time complexity: O(nlogn)O(n \log n)
  • Space complexity: O(n)O(n)

Common Pitfalls

Using the Wrong Heap Type

A critical mistake is confusing which heap to use for which purpose. You need a min-heap ordered by capital (to efficiently find affordable projects) and a max-heap ordered by profit (to select the most profitable affordable project). Reversing these or using the wrong ordering will produce incorrect results.

Not Transferring Projects When Capital Increases

After completing a project and gaining profit, your capital increases, which may unlock previously unaffordable projects. Forgetting to transfer newly affordable projects from the capital heap to the profit heap before selecting the next project means you might miss the most profitable option available to you.

Not Handling the Case When No Projects Are Affordable

If your current capital is insufficient to start any remaining projects, the max-heap for profits will be empty. Failing to check for this condition and break out of the loop will cause errors (such as attempting to pop from an empty heap) or infinite loops.

Incorrect Greedy Strategy

Some developers try to optimize by considering both profit and capital together (e.g., profit-to-capital ratio) or by looking ahead multiple projects. This overcomplicates the solution. The optimal greedy strategy is simple: always pick the highest-profit project among those currently affordable.

Off-by-One Errors with k Projects

Remember that you can complete at most k projects, not exactly k. If there are fewer than k affordable projects total or if you run out of affordable projects before completing k, you should stop early rather than erroring or returning incorrect values.