Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Solving Resource Allocation with the Two-Heaps Pattern

In our previous lessons, we built a sophisticated data structure using two heaps and lazy deletion to solve the Sliding Window Median problem. You've seen how this pattern can be used to maintain order and find central tendencies in a dynamic set of data. Now, we will explore how the same underlying pattern can be adapted to solve a completely different class of problems: resource allocation and optimization.

Today's lesson focuses on applying the two-heaps pattern to solve resource allocation problems, using the classic "IPO" (Initial Public Offering) problem as our case study. This problem is a perfect fit for your interests, as it models a fundamental challenge in finance and business: how to allocate limited capital to a set of available projects to maximize overall profit. You'll see how two heaps can elegantly manage the constraints of project cost and potential return.

Understanding the IPO Problem

The premise is straightforward: you start with an initial capital, w. You are presented with a list of projects, each with a required capital[i] to start and an expected profit[i] upon completion. You can undertake at most k projects. The goal is to select the projects that will result in the maximum possible final capital. When you complete a project, its profit is added to your capital, which you can then use for subsequent projects.

502. IPO - Solution & Explanation - NeetCode

Let's begin by formally understanding the problem statement. The NeetCode article provides a clear definition.

Read the "Description" section. Pay close attention to the inputs (k, w, profits, capital) and the core objective. Notice a key detail: the required capital is a prerequisite, not a cost that is deducted from your total.

The Greedy Strategy: What's the Optimal Choice?

With k projects to choose, and our capital growing over time, we need a strategy. At any given moment, we have a certain amount of capital, w. This means we can only consider projects where capital[i] <= w. Among this set of affordable projects, which one should we choose?

To maximize our final capital, it is always best to choose the affordable project that yields the highest profit. This is a greedy approach. By taking the biggest possible profit now, we maximize our capital for the next round, which in turn unlocks the widest possible range of new projects. This greedy choice—always picking the maximum profit from the set of currently feasible options—is the key insight that drives the solution.

Structuring the Solution with Two Heaps

The greedy strategy presents a challenge: how do we efficiently manage the two competing factors?

  1. We need to find all projects that are affordable with our current capital w.
  2. From that group, we need to find the one with the maximum profit.

This is where the two-heaps pattern comes into play. We can maintain two separate priority queues:

  • A Min-Heap for Capital: This heap will store all the projects we cannot yet afford. It will be ordered by capital. This allows us to efficiently find the projects that require the least capital to start. As our own capital w increases, we can pop projects from this heap that have just become affordable.
  • A Max-Heap for Profits: This heap will store all the projects we can currently afford. It is ordered by profit. This allows us to instantly select the most profitable project available to us at any time.

The following video from NeetCode provides an excellent visual and intuitive explanation of this two-heap structure.

IPO - Leetcode 502 - Python

This part of the video explains why two heaps are the right tool for this job.

Watch from the main explanation. The narrator clearly breaks down the logic of having a min-heap for projects sorted by capital and a max-heap for affordable projects sorted by profit.

The Algorithm in Action

Based on this two-heap strategy, our algorithm flows as follows:

  1. Initialization:
    • Create a min-heap (let's call it capital_heap) and push all projects onto it, using their capital requirement as the key. Each element in the heap could be a pair (capital, profit).
    • Create an empty max-heap (let's call it profit_heap).
  2. Main Loop: Iterate k times (since we can do at most k projects).
    • Transfer Affordable Projects: While capital_heap is not empty and the project at its top has a capital requirement less than or equal to our current capital w, pop it from capital_heap and push its profit into profit_heap.
    • Select Best Project: If profit_heap is empty, it means there are no affordable projects we can start. We can't make any more money, so we break the loop.
    • Otherwise, pop the top element from profit_heap (which is the largest available profit) and add it to our capital w.
  3. Return: After the loop finishes (either by completing k projects or running out of options), return the final capital w.

Let's trace this with Example 1 from the problem description: profits = [1, 2, 3], capital = [0, 1, 2], k = 2, w = 1.

  1. Init:

    • capital_heap (min-heap by capital): [(0, 1), (1, 2), (2, 3)]
    • profit_heap (max-heap by profit): []
    • w = 1
  2. Loop 1 (i=0):

    • Transfer:
      • capital_heap.top() is (0, 1). Since 0 <= w(1), pop it and push profit 1 to profit_heap. capital_heap is now [(1, 2), (2, 3)]. profit_heap is [1].
      • capital_heap.top() is (1, 2). Since 1 <= w(1), pop it and push profit 2 to profit_heap. capital_heap is now [(2, 3)]. profit_heap is now [2, 1].
      • capital_heap.top() is (2, 3). 2 > w(1), so we stop transferring.
    • Select: profit_heap is not empty. Pop 2. Update w = 1 + 2 = 3.
  3. Loop 2 (i=1):

    • Transfer:
      • capital_heap.top() is (2, 3). Since 2 <= w(3), pop it and push profit 3 to profit_heap. capital_heap is now empty. profit_heap is now [3, 1].
    • Select: profit_heap is not empty. Pop 3. Update w = 3 + 3 = 6.
  4. End Loop: We have completed k=2 projects.

  5. Return: w = 6.

Implementation and an Alternative Approach

Now that you understand the logic, let's look at a concrete implementation and a common optimization.

IPO - Leetcode 502 - Python

The same NeetCode video provides a clean Python implementation that matches our algorithm.

Please watch the code walkthrough from this timestamp. Notice how a min-heap is used to simulate a max-heap in Python by storing negative profit values. This is a common and important trick.

While the two-heap approach is perfectly valid, there's a slightly different method that is often more performant in practice. Instead of using a min-heap for capital, what if we just sorted all the projects by their capital requirement upfront?

502. IPO - Solution & Explanation - NeetCode

This section of the NeetCode article explains an optimized approach.

Read the section on the "Sorting + Max-Heap" approach. The core idea is to replace the min-heap for capital with a one-time sort. We then use a pointer to iterate through the sorted projects, adding newly affordable ones to the profit max-heap as our capital grows. This often has better cache performance than repeated min-heap operations.

This sorting-based approach has the same theoretical time complexity, , but can be faster in real-world scenarios. Recognizing this alternative demonstrates a deeper level of problem-solving, moving from the direct pattern application to a more tailored optimization.

Finally, being aware of common mistakes is crucial for robust implementation, especially in an interview setting.

502. IPO - Solution & Explanation - NeetCode

This section highlights frequent errors that can occur when solving this problem.

Quickly review the "Common Pitfalls" section. Pay special attention to "Not Handling the Case When No Projects Are Affordable"—this corresponds to the break condition in our algorithm when the profit heap is empty.

Conclusion

In this lesson, you've seen how the versatile two-heaps pattern can be applied to a resource allocation problem. By using one heap to manage eligibility (capital) and another to optimize selection (profit), we can construct an efficient, greedy solution.

Key Takeaways:

  • The IPO problem can be solved with a greedy strategy: always choose the most profitable project you can currently afford.
  • The two-heaps pattern is a natural fit for this strategy. A min-heap manages projects sorted by capital, and a max-heap manages affordable projects sorted by profit.
  • The algorithm iteratively moves affordable projects from the capital heap to the profit heap and selects the best one.
  • An alternative, often faster, approach involves pre-sorting projects by capital instead of using a min-heap.
  • The time complexity for both approaches is dominated by sorting or heapifying all N projects, leading to . The space complexity is to store the projects.

You have now applied the two-heaps pattern to both statistical analysis (medians) and financial optimization (IPO). This demonstrates your ability to recognize the underlying structure of a problem and map it to a known algorithmic pattern. In our final lesson for this module, we will consolidate our understanding by formally analyzing the time and space complexity of the heap-based solutions we've covered.

Can't find a good explanation? Sign up and we'll make it for you

Sign up