Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

502. IPO - Leetcode Solution

Problem Description

The IPO problem on Leetcode asks you to maximize your capital by selecting up to k projects to invest in, given a starting capital W. Each project requires a certain amount of capital to start (given by the Capital array) and yields a profit (given by the Profits array) upon completion. You can only start a project if your current capital is at least the required capital for that project. After completing a project, you immediately add its profit to your capital and can use it for subsequent investments. Your goal is to pick at most k projects in such a way that your final capital is maximized. Each project can be selected at most once.

  • You may select up to k different projects.
  • You cannot select the same project more than once.
  • At each step, you may only choose from projects you can currently afford (i.e., Capital[i] ≤ current capital).
  • Profits are added to your capital immediately after completing a project.

Thought Process

At first glance, it might seem reasonable to try all combinations of k projects to find the one with the highest final capital. However, this brute-force approach is computationally infeasible for large inputs because the number of combinations grows exponentially.

Instead, we need to make greedy choices: at every step, among all projects we can currently afford, we should pick the one with the highest profit. This ensures we maximize our capital at each stage, allowing us to afford more lucrative projects in the future. To do this efficiently, we need a way to quickly:

  • Find all projects we can currently afford.
  • Among those, select the one with the highest profit.

This leads us to consider priority queues (heaps) for efficient selection, and sorting to efficiently track which projects become affordable as our capital increases.

Solution Approach

Let's break down the algorithm step by step:

  1. Pair Projects: Combine each project's required capital and profit into a pair for easy management.
  2. Sort by Capital: Sort all projects by their required capital in ascending order. This allows us to efficiently find which projects become available as our capital increases.
  3. Use a Max-Heap for Profits: As we iterate, we maintain a max-heap (priority queue) of all projects we can currently afford, ordered by profit. This allows us to always pick the most profitable project available.
  4. Iterate up to k Times: For up to k selections:
    • Add all projects whose capital requirement is less than or equal to our current capital to the max-heap.
    • If the heap is empty, we can't afford any more projects, so we break early.
    • Otherwise, pop the most profitable project from the heap and add its profit to our capital.
  5. Return Final Capital: After up to k iterations, return the final value of our capital.

We use a max-heap for profits because at each step we want the maximum profit among available projects. We sort by capital so we can efficiently "unlock" new projects as our capital grows.

Example Walkthrough

Input:
k = 2, W = 0, Profits = [1,2,3], Capital = [0,1,1]

  1. Initial State: Capital = 0. Projects: [(0,1), (1,2), (1,3)] after sorting by capital.
  2. First Iteration:
    • We can afford projects with required capital ≤ 0: Only project 0 (profit 1).
    • Add project 0 to the max-heap (profit 1).
    • Select and complete project 0. Capital becomes 0 + 1 = 1.
  3. Second Iteration:
    • Now capital = 1. We can afford projects with required capital ≤ 1: projects 1 (profit 2) and 2 (profit 3).
    • Add both to the max-heap. Heap now contains profits [3, 2].
    • Select and complete project 2 (profit 3). Capital becomes 1 + 3 = 4.
  4. Done: We have completed 2 projects. Return capital = 4.

Time and Space Complexity

  • Brute-force Approach: Tries all combinations of up to k projects, leading to exponential time complexity: O(n^k).
  • Optimized Approach:
    • Sorting the projects: O(n \log n)
    • Each project is pushed and popped from the heap at most once: O(n \log n)
    • Overall: O(n \log n + k \log n), which simplifies to O(n \log n)
    • Space complexity: O(n) for the heap and project list

Summary

The IPO problem is a classic example of using a greedy strategy with the help of efficient data structures. By always picking the most profitable project we can afford and updating our available projects as our capital grows, we ensure we make the best local (and thus global) decisions. The use of sorting and a max-heap allows us to efficiently manage which projects are available and which one to pick next, resulting in an elegant and performant solution.

Code Implementation

import heapq

class Solution:
    def findMaximizedCapital(self, k, W, Profits, Capital):
        projects = sorted(zip(Capital, Profits))
        max_profit_heap = []
        i = 0
        n = len(Profits)
        for _ in range(k):
            while i < n and projects[i][0] <= W:
                heapq.heappush(max_profit_heap, -projects[i][1])
                i += 1
            if not max_profit_heap:
                break
            W -= heapq.heappop(max_profit_heap)
        return W
      
#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;
        int n = Profits.size();
        for (int i = 0; i < n; ++i) {
            projects.push_back({Capital[i], Profits[i]});
        }
        sort(projects.begin(), projects.end());
        priority_queue<int> maxProfitHeap;
        int idx = 0;
        for (int i = 0; i < k; ++i) {
            while (idx < n && projects[idx].first <= W) {
                maxProfitHeap.push(projects[idx].second);
                ++idx;
            }
            if (maxProfitHeap.empty()) break;
            W += maxProfitHeap.top();
            maxProfitHeap.pop();
        }
        return W;
    }
};
      
import java.util.*;

class Solution {
    public int findMaximizedCapital(int k, int W, int[] Profits, int[] Capital) {
        int n = Profits.length;
        int[][] projects = new int[n][2];
        for (int i = 0; i < n; i++) {
            projects[i][0] = Capital[i];
            projects[i][1] = Profits[i];
        }
        Arrays.sort(projects, (a, b) -> Integer.compare(a[0], b[0]));
        PriorityQueue<Integer> maxProfitHeap = new PriorityQueue<>(Collections.reverseOrder());
        int idx = 0;
        for (int i = 0; i < k; i++) {
            while (idx < n && projects[idx][0] <= W) {
                maxProfitHeap.add(projects[idx][1]);
                idx++;
            }
            if (maxProfitHeap.isEmpty()) break;
            W += maxProfitHeap.poll();
        }
        return W;
    }
}
      
class MaxHeap {
    constructor() {
        this.heap = [];
    }
    push(val) {
        this.heap.push(val);
        let idx = this.heap.length - 1;
        while (idx > 0) {
            let parent = Math.floor((idx - 1) / 2);
            if (this.heap[parent] >= this.heap[idx]) break;
            [this.heap[parent], this.heap[idx]] = [this.heap[idx], this.heap[parent]];
            idx = parent;
        }
    }
    pop() {
        if (this.heap.length === 0) return null;
        const top = this.heap[0];
        const end = this.heap.pop();
        if (this.heap.length === 0) return top;
        this.heap[0] = end;
        let idx = 0;
        while (true) {
            let left = 2 * idx + 1, right = 2 * idx + 2;
            let largest = idx;
            if (left < this.heap.length && this.heap[left] > this.heap[largest]) largest = left;
            if (right < this.heap.length && this.heap[right] > this.heap[largest]) largest = right;
            if (largest === idx) break;
            [this.heap[largest], this.heap[idx]] = [this.heap[idx], this.heap[largest]];
            idx = largest;
        }
        return top;
    }
    isEmpty() {
        return this.heap.length === 0;
    }
}

var findMaximizedCapital = function(k, W, Profits, Capital) {
    let projects = Capital.map((c, i) => [c, Profits[i]]);
    projects.sort((a, b) => a[0] - b[0]);
    let heap = new MaxHeap();
    let idx = 0, n = Profits.length;
    for (let i = 0; i < k; i++) {
        while (idx < n && projects[idx][0] <= W) {
            heap.push(projects[idx][1]);
            idx++;
        }
        if (heap.isEmpty()) break;
        W += heap.pop();
    }
    return W;
};