BackhardGreedy

Optimizing Rational Selection Solution

Problem Statement

Given a set of rational numbers, each defined as a pair of numerator and denominator, and a capacity constraint, select a subset of these rationals to maximize the total value. The value of each fraction is determined by its magnitude (numerator/denominator). The total value of the selected fractions should not exceed the given capacity.

Example 1
Input
[[1, 2], [1, 3], [2, 3]], 2
Output
1.75

Explanation: Step-by-step: with input [[1, 2], [1, 3], [2, 3]] and capacity 2, we first calculate the value of each fraction: 0.5, 0.3333, 0.6667. Then, we sort these fractions in descending order: 0.6667, 0.5, 0.3333. We select the first fraction (0.6667) and add it to our total value (0.6667). Since our total value (0.6667) is less than the capacity (2), we can add the next fraction (0.5) to our total value (1.1667). Again, since our total value (1.1667) is less than the capacity (2), we can add the next fraction (0.5833) to our total value (1.75), which exceeds the capacity. So, we stop here and return 1.75.

Example 2
Input
[[1, 2], [1, 3], [2, 3]], 1.5
Output
1.1667

Explanation: Step-by-step: with input [[1, 2], [1, 3], [2, 3]] and capacity 1.5, we first calculate the value of each fraction: 0.5, 0.3333, 0.6667. Then, we sort these fractions in descending order: 0.6667, 0.5, 0.3333. We select the first fraction (0.6667) and add it to our total value (0.6667). Since our total value (0.6667) is less than the capacity (1.5), we can add the next fraction (0.5) to our total value (1.1667), which is still less than the capacity. So, we stop here and return 1.1667.

Constraints

  • 1 <= number of rational numbers <= 100
  • Each numerator and denominator is in the range [1, 1000].
  • 1 <= capacity <= 1000
  • Denominators are non-zero and fractions are in simplest form.
Live Compiler1 Free Run Available
Loading Editor...
Test Cases & Output
Click "Run" to test your 1 free compile trial!

🚀 Practice this problem

Run code, get AI hints & track streak

Sign Up Free

Optimizing Rational Selection — Problem Statement & Solution Guide

GreedyHardFractional Knapsack
TimeO(n·C/wordSize) with DP or O(2^{n/2}) with meet‑in‑the‑middle
|
SpaceO(C) for DP bitset or O(2^{n/2}) for meet‑in‑the‑middle

Problem Description

Given a set of rational numbers, each defined as a pair of numerator and denominator, and a capacity constraint, select a subset of these rationals to maximize the total value. The value of each fraction is determined by its magnitude (numerator/denominator). The total value of the selected fractions should not exceed the given capacity.

DSA Pattern Breakdown

DSA Pattern Breakdown

"Optimizing Rational Selection"

hard

WHY DOES IT MATTER?

The subset‑sum pattern is essential because it transforms an NP‑hard combinatorial selection into a structured DP or meet‑in‑the‑middle problem, enabling polynomial or sub‑exponential solutions that are acceptable in interview settings.

OPTIMIZATION CHALLENGE

The key insight is to convert rational weights to a common integer scale or to use a bitset to compress the DP state, dramatically reducing both time and memory compared to naïve enumeration.

REAL-WORLD CONNECTION

In distributed systems, this pattern mirrors the allocation of limited bandwidth to a set of data streams where each stream consumes a fractional amount of bandwidth; the goal is to maximize throughput without exceeding the link capacity.

When implementing the DP, always normalize the capacity first and use a bitset (e.g., Python’s int as a bit array) to achieve O(n·C/wordSize) performance; this trick often surprises interviewers and demonstrates deep understanding.

COMPLEXITY AT A GLANCE

⏱ Time:O(n·C/wordSize) with DP or O(2^{n/2}) with meet‑in‑the‑middle
💾 Space:O(C) for DP bitset or O(2^{n/2}) for meet‑in‑the‑middle

Core Theory — Why This Approach?

The problem reduces to a classic 0/1 knapsack where each item’s weight equals its value, which is the magnitude of a rational number (numerator divided by denominator). Because all weights are positive and we must stay within a capacity, the goal is to find a subset whose sum is as large as possible without exceeding the limit. Naïve enumeration of all subsets is exponential (O(2^n)) and infeasible for large n. The optimal paradigm is to use dynamic programming that exploits the additive structure of the problem. By converting each rational to an integer weight via a common denominator (or scaling by the least common multiple of all denominators), we can apply a classic DP that tracks achievable sums up to the capacity. A bitset implementation of this DP runs in O(n·C/wordSize) time and O(C) space, where C is the integer capacity after scaling. For very large capacities or when the number of items is moderate, a meet‑in‑the‑middle approach splits the set into two halves, enumerates all subset sums of each half, and then uses two‑pointer or binary search to find the best pair that fits the capacity. This reduces the time to O(2^{n/2}) and space to O(2^{n/2}), which is often practical for n up to 40–50.

Interview Questions on This Problem

Q1How would you adapt the classic 0/1 knapsack DP to handle rational weights that are not integers?

First, find the least common multiple (LCM) of all denominators to convert each rational weight into an integer by multiplying numerator and denominator appropriately. Then run the standard DP on these integer weights. If the LCM is too large, consider scaling or using a floating‑point approximation with careful rounding, or switch to a meet‑in‑the‑middle strategy that works directly with the original fractions.

Q2A fintech platform needs to allocate a limited budget across multiple investment opportunities, each with a fractional expected return. Which algorithmic pattern would you recommend and why?

Use a meet‑in‑the‑middle subset‑sum approach because the number of opportunities is usually moderate (tens to low hundreds) but the budget (capacity) can be large. This pattern guarantees optimality while keeping runtime manageable, and it can be implemented with two sorted lists and a two‑pointer scan to find the best combination.

Q3During a coding interview, you’re asked to explain why a greedy strategy that always picks the largest fraction first fails for this problem. What’s the key counter‑example you would present?

Consider fractions 3/4 and 2/3 with a capacity of 1. Greedy picks 3/4 (0.75) leaving 0.25 capacity, but the optimal solution is to pick 2/3 (≈0.667) and 3/4 cannot be added. The greedy choice leaves a larger unused capacity, whereas the optimal picks a different subset that fits better.

Examples

Example 1

Input

[[1, 2], [1, 3], [2, 3]], 2

Output

1.75

Explanation: Step-by-step: with input [[1, 2], [1, 3], [2, 3]] and capacity 2, we first calculate the value of each fraction: 0.5, 0.3333, 0.6667. Then, we sort these fractions in descending order: 0.6667, 0.5, 0.3333. We select the first fraction (0.6667) and add it to our total value (0.6667). Since our total value (0.6667) is less than the capacity (2), we can add the next fraction (0.5) to our total value (1.1667). Again, since our total value (1.1667) is less than the capacity (2), we can add the next fraction (0.5833) to our total value (1.75), which exceeds the capacity. So, we stop here and return 1.75.

Example 2

Input

[[1, 2], [1, 3], [2, 3]], 1.5

Output

1.1667

Explanation: Step-by-step: with input [[1, 2], [1, 3], [2, 3]] and capacity 1.5, we first calculate the value of each fraction: 0.5, 0.3333, 0.6667. Then, we sort these fractions in descending order: 0.6667, 0.5, 0.3333. We select the first fraction (0.6667) and add it to our total value (0.6667). Since our total value (0.6667) is less than the capacity (1.5), we can add the next fraction (0.5) to our total value (1.1667), which is still less than the capacity. So, we stop here and return 1.1667.

Constraints

  • 1 <= number of rational numbers <= 100
  • Each numerator and denominator is in the range [1, 1000].
  • 1 <= capacity <= 1000
  • Denominators are non-zero and fractions are in simplest form.

Optimal Approach & Strategy

Convert fractions to integer weights using a common denominator, then run a DP or bitset to find the maximum achievable sum ≤ capacity in O(n·C/wordSize) time, or use meet‑in‑the‑middle for very large capacities.

Brute Force Approach

Enumerate all subsets of fractions, compute each subset’s total value, and keep the best one that stays within capacity. This takes O(2^n) time and is impractical for large n.

Verified Code Solutions

JavaScript Solution
Time: O(n·C/wordSize) with DP or O(2^{n/2}) with meet‑in‑the‑middle
function solution(fractions, capacity) {
      // Calculate the value of each fraction
      let values = fractions.map(([num, denom]) => num / denom);
      
      // Sort the fractions in descending order
      let sortedFractions = fractions.sort((a, b) => (b[0] / b[1]) - (a[0] / a[1]));
      
      let totalValue = 0;
      let result = 0;
      
      for (let i = 0; i < sortedFractions.length; i++) {
         let currentValue = sortedFractions[i][0] / sortedFractions[i][1];
         if (totalValue + currentValue <= capacity) {
            totalValue += currentValue;
            result = totalValue;
         } else {
            break;
         }
      }
      
      return result;
   }

Solve in Interative Editor

Ready to test your code? Open our built-in compiler, run custom test suites, and see detailed complexity analysis reports instantly.