Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1421. NPV Queries - Leetcode Solution

Problem Description

The NPV Queries problem involves evaluating a series of cash flows and answering queries about their Net Present Value (NPV). You are given an array cashflows where cashflows[i] represents the cash flow at time i. You are also given a list of queries, where each query consists of a discount rate r. For each query, you must compute the NPV of the cash flows using the given discount rate.

The Net Present Value for a discount rate r is calculated as:

NPV = cashflows[0] + cashflows[1]/(1+r) + cashflows[2]/(1+r)^2 + ... + cashflows[n-1]/(1+r)^(n-1)

  • Each query is independent.
  • Return the NPV for each query, rounded to five decimal places.
  • Constraints: 1 <= len(cashflows) <= 10^4, 1 <= len(queries) <= 10^4, -10^4 <= cashflows[i] <= 10^4, -0.99 <= r <= 10

Thought Process

To solve this problem, the first thought is to directly compute the NPV for each query by iterating through the cashflows array and applying the formula. For each query, we would recalculate the denominator for each cash flow, which involves exponentiation. With up to 10,000 queries and 10,000 cash flows, a naive approach could result in up to 100 million computations, which may be slow.

To optimize, we can look for patterns or opportunities to avoid redundant calculations. Since each query uses a different discount rate, we cannot precompute denominators. However, we can use iterative multiplication to avoid repeated exponentiation. By keeping track of the current power of (1 + r) as we iterate, we can compute each term efficiently.

Solution Approach

Here’s a step-by-step breakdown of the optimized solution:

  1. For each query:
    • Initialize a variable npv to 0.
    • Initialize a variable denominator to 1 (since (1 + r)^0 = 1).
    • Iterate through the cashflows array:
      • Add cashflows[i] / denominator to npv.
      • Update denominator *= (1 + r) for the next term.
    • After processing all cash flows, round npv to five decimal places.
  2. Return the list of NPVs for all queries.

This approach avoids expensive exponentiation by using cumulative multiplication, making the solution much faster.

Example Walkthrough

Suppose cashflows = [1000, -500, 200] and queries = [0.1, 0.05].

  • For r = 0.1:
    • Year 0: 1000 / 1 = 1000.0
    • Year 1: -500 / 1.1 ≈ -454.54545
    • Year 2: 200 / 1.21 ≈ 165.28926
    • NPV ≈ 1000.0 - 454.54545 + 165.28926 ≈ 710.74381
  • For r = 0.05:
    • Year 0: 1000 / 1 = 1000.0
    • Year 1: -500 / 1.05 ≈ -476.19048
    • Year 2: 200 / 1.1025 ≈ 181.48820
    • NPV ≈ 1000.0 - 476.19048 + 181.48820 ≈ 705.29772

The output would be [710.74381, 705.29772].

Time and Space Complexity

  • Brute-force Approach:
    • Time Complexity: O(Q * N), where Q is the number of queries and N is the number of cash flows. Each query computes each term separately, possibly using exponentiation.
    • Space Complexity: O(Q) for the results.
  • Optimized Approach (iterative multiplication):
    • Time Complexity: O(Q * N), but with a smaller constant factor since we avoid repeated exponentiation.
    • Space Complexity: O(Q) for the output list.

The optimized approach is much faster in practice due to efficient term calculation.

Summary

The NPV Queries problem requires computing the net present value of a series of cash flows for multiple discount rates. By recognizing that exponentiation can be replaced with iterative multiplication, we achieve a fast and efficient solution suitable for large inputs. This approach demonstrates the power of mathematical insight and computational optimization in algorithm design.

Code Implementation

def npvQueries(cashflows, queries):
    res = []
    n = len(cashflows)
    for r in queries:
        npv = 0.0
        denom = 1.0
        for cf in cashflows:
            npv += cf / denom
            denom *= (1 + r)
        res.append(round(npv, 5))
    return res
      
#include <vector>
#include <cmath>
#include <iomanip>

std::vector<double> npvQueries(const std::vector<int>& cashflows, const std::vector<double>& queries) {
    std::vector<double> res;
    for (double r : queries) {
        double npv = 0.0;
        double denom = 1.0;
        for (int cf : cashflows) {
            npv += cf / denom;
            denom *= (1 + r);
        }
        // Round to 5 decimal places
        npv = std::round(npv * 1e5) / 1e5;
        res.push_back(npv);
    }
    return res;
}
      
import java.util.*;

public class Solution {
    public List<Double> npvQueries(int[] cashflows, double[] queries) {
        List<Double> res = new ArrayList<>();
        for (double r : queries) {
            double npv = 0.0;
            double denom = 1.0;
            for (int cf : cashflows) {
                npv += cf / denom;
                denom *= (1 + r);
            }
            // Round to 5 decimal places
            npv = Math.round(npv * 1e5) / 1e5;
            res.add(npv);
        }
        return res;
    }
}
      
function npvQueries(cashflows, queries) {
    const res = [];
    for (const r of queries) {
        let npv = 0.0;
        let denom = 1.0;
        for (const cf of cashflows) {
            npv += cf / denom;
            denom *= (1 + r);
        }
        // Round to 5 decimal places
        npv = Math.round(npv * 1e5) / 1e5;
        res.push(npv);
    }
    return res;
}