Skip to main content

Fractional Knapsack

MediumProblemExternal link

Description

You are given two arrays, val[] and wt[]: item i is worth val[i] and weighs wt[i] kilograms. You also have a knapsack that can carry at most capacity kilograms.

You may put any of the items in the knapsack, and you may cut an item and take only part of it. A part keeps its share of the value: half of an item is worth half of its value.

Return the maximum total value you can carry, as a decimal number rounded to 6 decimal places.

Examples

Example 1

Input: val = [60, 40, 90, 50], wt = [20, 10, 45, 50], capacity = 60

Output: 160.000000

Explanation: Per kilogram the items are worth 3, 4, 2 and 1. Take all of item 1 (10 kg, worth 40) and all of item 0 (20 kg, worth 60). That leaves 30 kg of space, filled with 30 of item 2's 45 kg: 90 × 30 / 45 = 60. The total is 40 + 60 + 60 = 160.

Example 2

Input: val = [10, 20], wt = [5, 5], capacity = 100

Output: 30.000000

Explanation: The items weigh 10 kg together, well under 100, so both go in whole.

Example 3

Input: val = [100], wt = [30], capacity = 10

Output: 33.333333

Explanation: The only item is too heavy, so take 10 of its 30 kg: 100 × 10 / 30 = 33.333333.

Constraints

  • val.size() = wt.size() ≤ 10^5
  • 1 ≤ val[i], wt[i] ≤ 10^4
  • 1 ≤ capacity ≤ 10^9

Editorial

Brute Force

Intuition

Think in kilograms. Because items can be cut, the bag is really capacity kilograms to fill, and each kilogram of item i is worth val[i] / wt[i]. The best bag is one in which every kilogram is as valuable as it can be. So fill it with the most valuable kilograms first: take the item with the highest value per kilogram, as much of it as fits, then the next best, and so on, until the bag is full or the items run out.

The four items of Example 1 with their value per kilogram, 3, 4, 2 and 1, and the 60 kg bag filled in that order: all of item 1 (+40), all of item 0 (+60), and 30 of item 2's 45 kg (+60), for 160. Item 3 is left out.

Why this is always best. Suppose some bag leaves part of a better item out while carrying some kilograms of a worse one. Swap one kilogram of the worse item for one kilogram of the better one: the weight stays the same and the value goes up. So a best bag never does that. It takes items in order of value per kilogram, and only the last item it touches can be cut.

This works only because items can be cut. In 0/1 knapsack, where every item is all or nothing, the same rule can fail:

A 50 kg bag with A (60 for 10 kg), B (100 for 20 kg) and C (120 for 30 kg). With cutting, A, B and two thirds of C give 240. Without cutting, the same rule takes A and B and leaves 20 kg empty, 160, while the real best is B and C, 220.

The rule takes A and B, then cannot fit C and leaves 20 kg empty, for 160, while B and C together give 220. With cutting, no space is ever wasted and the swap argument above always holds. That is why 0/1 knapsack needs dynamic programming and fractional knapsack does not.

The simplest way to follow the rule. Every round, look through all the items for the one with the most value per kilogram that is not used up yet, and take as much of it as fits.

Comparing value per kilogram without dividing. val[i] / wt[i] > val[j] / wt[j] says the same as val[i] × wt[j] > val[j] × wt[i], because both weights are positive. The products are whole numbers of at most 10^8, so the comparison is exact.

Step-by-Step Explanation

We trace Example 1. The top row is each item's value with its value per kilogram underneath, and the second row is how many of its kilograms are still out of the bag. Each round is a full scan, with the chosen item in blue, followed by taking it: green when taken whole, amber when cut.

Step 1: The bag holds 60 kg. Items are worth 3, 4, 2 and 1 per kilogram.

Step 2: Round 1: scan all 4 items; the best one left is item 1, 4 per kg.

Step 3: Take all 10 kg of item 1: +40. Total 40, 50 kg left.

Step 4: Round 2: scan all 4 items; the best one left is item 0, 3 per kg.

Step 5: Take all 20 kg of item 0: +60. Total 100, 30 kg left.

Step 6: Round 3: scan all 4 items; the best one left is item 2, 2 per kg.

Step 7: Cut item 2: 30 of 45 kg, +60. Total 160, 0 kg left.

Step 8: The bag is full: return 160.000000, after 12 looks at items.

Brute Force — Scan for the Best Item, Every Round — Example 1, a 60 kg bag. Top row: item values, with value per kilogram underneath. Second row: kilograms still out of the bag.

Algorithm

  1. Set left[i] = wt[i] for every item, and total = 0.
  2. While capacity > 0:
    • Scan every item and pick best: the one with left[best] > 0 and the most value per kilogram.
    • If there is none, every item is already in the bag: stop.
    • Let take = min(left[best], capacity). Add val[best] × take / wt[best] to total, and subtract take from both left[best] and capacity.
  3. Return total.

Code

#include <algorithm>
#include <vector>
using namespace std;

class Solution {
public:
    double fractionalKnapsack(vector<int>& val, vector<int>& wt, int capacity) {
        int n = val.size();
        vector<long long> left(wt.begin(), wt.end());  // kilograms of each item not yet in the bag
        double total = 0;
        long long cap = capacity;
        while (cap > 0) {
            int best = -1;
            for (int i = 0; i < n; i++)  // the item with the most value per kilogram among those not used up
                if (left[i] > 0 && (best == -1 || (long long)val[i] * wt[best] > (long long)val[best] * wt[i])) best = i;
            if (best == -1) break;  // every item is already in the bag
            long long take = min(left[best], cap);
            total += (double)val[best] * take / wt[best];
            left[best] -= take;
            cap -= take;
        }
        return total;
    }
};
class Solution:
    def fractionalKnapsack(self, val, wt, capacity):
        n = len(val)
        left = wt[:]  # kilograms of each item not yet in the bag
        total = 0.0
        while capacity > 0:
            best = -1
            for i in range(n):  # the item with the most value per kilogram among those not used up
                if left[i] > 0 and (best == -1 or val[i] * wt[best] > val[best] * wt[i]):
                    best = i
            if best == -1:  # every item is already in the bag
                break
            take = min(left[best], capacity)
            total += val[best] * take / wt[best]
            left[best] -= take
            capacity -= take
        return total
class Solution {
    public double fractionalKnapsack(int[] val, int[] wt, int capacity) {
        int n = val.length;
        long[] left = new long[n];  // kilograms of each item not yet in the bag
        for (int i = 0; i < n; i++) left[i] = wt[i];
        double total = 0;
        long cap = capacity;
        while (cap > 0) {
            int best = -1;
            for (int i = 0; i < n; i++)  // the item with the most value per kilogram among those not used up
                if (left[i] > 0 && (best == -1 || (long) val[i] * wt[best] > (long) val[best] * wt[i])) best = i;
            if (best == -1) break;  // every item is already in the bag
            long take = Math.min(left[best], cap);
            total += (double) val[best] * take / wt[best];
            left[best] -= take;
            cap -= take;
        }
        return total;
    }
}

Complexity Analysis

Time Complexity: O(n²). Every round either uses up an item or fills the bag, so there are at most n rounds, and each one scans all n items.

Space Complexity: O(n) for left.

Why This Approach Is Not Efficient

Each round scans every item again, although the ranking of the items never changes: the item that is second best now becomes the best as soon as the first one is used up. Measured in Python, 1,000 items take 0.017 s, 2,000 take 0.069 s, 4,000 take 0.285 s and 8,000 take 1.1 s, four times longer every time the count doubles. At that rate the full 10^5 items would take about 3 minutes.

Since the order never changes, it can be worked out once, by sorting.

Better Approach - Sort by Value per Kilogram

Intuition

Sort once, then fill. Put the items in order of value per kilogram, highest first, and walk down the list. While an item fits whole, take it. The first item that does not fit is cut to fill exactly the space that is left, and then the bag is full.

Sorting by value per kilogram. C++ and Java compare two items with the cross-multiplication above, so no decimals are involved. Python sorts by the decimal val[i] / wt[i], which is exact enough here: with whole numbers up to 10^4, two different ratios differ by at least 1/10^8, far more than a decimal's rounding error, and equal ratios such as 2/4 and 1/2 give exactly the same decimal. Items with equal value per kilogram can go in any order, since a kilogram of one is worth exactly a kilogram of the other.

The total. A whole item adds exactly val[i]. Only the cut item needs a division, val[i] × space / wt[i].

When everything fits. If the capacity is at least the total weight, the loop takes every item and never cuts one, as in Example 2. The totals stay below 10^9, and a double holds them far more precisely than the 6 decimal places asked for.

Step-by-Step Explanation

We trace Example 1 again. The first frame shows the items as given and the second shows them sorted by value per kilogram; then the bag is filled from the left. Items taken whole are green, and the cut item is amber.

Step 1: The items as given, worth 3, 4, 2 and 1 per kilogram.

Step 2: Sort once by value per kilogram: item 1, item 0, item 2, item 3.

Step 3: Item 1 (10 kg) fits whole: +40, total 40, 50 kg left.

Step 4: Item 0 (20 kg) fits whole: +60, total 100, 30 kg left.

Step 5: Item 2 does not fit whole: cut it, 90 × 30 / 45 = 60. Total 160. Stop.

Step 6: Return 160.000000. Item 3 was never needed.

Sort by Value per Kilogram, Then Fill — Example 1: the items are sorted once by value per kilogram, then taken from the left until the bag is full.

Algorithm

  1. Sort the item indices by val[i] / wt[i], highest first.
  2. Set total = 0. For each item i in that order:
    • if wt[i] ≤ capacity, add val[i] to total and subtract wt[i] from capacity;
    • otherwise add val[i] × capacity / wt[i] to total and stop.
  3. Return total.

Code

#include <algorithm>
#include <numeric>
#include <vector>
using namespace std;

class Solution {
public:
    double fractionalKnapsack(vector<int>& val, vector<int>& wt, int capacity) {
        vector<int> order(val.size());
        iota(order.begin(), order.end(), 0);
        sort(order.begin(), order.end(), [&](int a, int b) {  // a before b when val[a]/wt[a] > val[b]/wt[b], without dividing
            return (long long)val[a] * wt[b] > (long long)val[b] * wt[a];
        });
        double total = 0;
        long long cap = capacity;
        for (int i : order) {
            if (wt[i] <= cap) {  // the whole item fits
                total += val[i];
                cap -= wt[i];
            } else {  // only part of it fits, and then the bag is full
                total += (double)val[i] * cap / wt[i];
                break;
            }
        }
        return total;
    }
};
class Solution:
    def fractionalKnapsack(self, val, wt, capacity):
        # most value per kilogram first; with whole numbers up to 10^4, equal ratios give equal decimals
        order = sorted(range(len(val)), key=lambda i: val[i] / wt[i], reverse=True)
        total = 0.0
        for i in order:
            if wt[i] <= capacity:  # the whole item fits
                total += val[i]
                capacity -= wt[i]
            else:  # only part of it fits, and then the bag is full
                total += val[i] * capacity / wt[i]
                break
        return total
import java.util.Arrays;

class Solution {
    public double fractionalKnapsack(int[] val, int[] wt, int capacity) {
        Integer[] order = new Integer[val.length];
        for (int i = 0; i < order.length; i++) order[i] = i;
        // a before b when val[a]/wt[a] > val[b]/wt[b], compared without dividing
        Arrays.sort(order, (a, b) -> Long.compare((long) val[b] * wt[a], (long) val[a] * wt[b]));
        double total = 0;
        long cap = capacity;
        for (int i : order) {
            if (wt[i] <= cap) {  // the whole item fits
                total += val[i];
                cap -= wt[i];
            } else {  // only part of it fits, and then the bag is full
                total += (double) val[i] * cap / wt[i];
                break;
            }
        }
        return total;
    }
}

Complexity Analysis

Time Complexity: O(n log n) for the sort; the filling afterwards is O(n). Measured in Python, 0.026 s for 10^5 items.

Space Complexity: O(n) for the sorted list of indices.

Why This Approach Is Not Efficient

Sorting puts every item in its exact place, but the answer depends on one question only: which item gets cut. Everything before it goes in whole, everything after it stays out, and the order among those items is never used. Sorting pays O(n log n) to arrange items whose order does not matter.

It is still a very good answer, and it is GfG's expected complexity. The next approach finds the cut item directly, in O(n) expected time.

Optimal Approach - Find the Cut Item Without Sorting

Intuition

Find the cut item, not the whole order. Pick one item at random as a pivot and split the items, by value per kilogram, into three groups: better than the pivot, equal to it, and worse. Then weigh the better group.

  • If it weighs at least as much as the space left, the bag fills up inside it, so the cut item is in there. Everything else can be forgotten; continue with just the better group.
  • Otherwise, the whole better group goes in, in any order. Then, if the pivot's group (the items worth exactly as much per kilogram as the pivot) fills the rest, cut it and stop; all of it is worth the same per kilogram, so which of them is cut does not matter. If not, it goes in whole too, and the search continues among the worse items with the space that is left.
One round with item 2 (2 per kg, 45 kg) as the pivot: items 1 and 0 are better and weigh 30 kg, less than the 60 kg of space, so they go in whole (+100); the pivot fills the remaining 30 kg and is cut (+60); item 3, which is worse, is never looked at again.

This is the idea behind quickselect, the method for finding the k-th smallest number without sorting.

Why about n steps. Each round reads the remaining items once. Half of the time, a random pivot lands in the middle half of them, and then at most three quarters of them survive into the next round. So on average the group shrinks steadily, and the total work, n plus a fraction of n plus a smaller fraction, stays a small multiple of n. Always picking a bad pivot would cost O(n²), which is why the pivot is picked at random.

Step-by-Step Explanation

We trace Example 1. The pivot is picked at random; in this run the picks happen to be item 3, then item 1, then item 2. The pivot is blue, the group being weighed is amber, items in the bag are green and the cut item is orange. An item set aside for good shows a dot instead of its weight in the second row.

Step 1: Four candidates, nothing sorted, 60 kg of space.

Step 2: Pivot item 3 (1/kg): items 0, 1 and 2 are better than it (75 kg), against 60 kg of space.

Step 3: 75 ≥ 60: keep only the better group. Item 3 is set aside for good.

Step 4: Pivot item 1 (4/kg): nothing is better than it (0 kg), against 60 kg of space.

Step 5: Nothing is better. Item 1 fits whole: total 40, 50 kg left. Continue with the worse items.

Step 6: Pivot item 2 (2/kg): item 0 is better than it (20 kg), against 50 kg of space.

Step 7: The better group goes in whole (+60). Item 2 is the cut item: +60, total 160. Return 160.000000.

Find the Cut Item With a Random Pivot — Example 1. Each round picks a pivot, weighs the items that are better per kilogram, and keeps only the side where the bag fills up.

Algorithm

  1. Let items be every index, and total = 0.
  2. While items is not empty and capacity > 0:
    • Pick a random pivot p from items, and split items into better, same and worse by comparing val[i] × wt[p] with val[p] × wt[i].
    • If the better group weighs at least capacity, keep only better and repeat.
    • Otherwise add the better group's value to total and subtract its weight from capacity. If the same group weighs at least the remaining capacity, return total + val[p] × capacity / wt[p]. If not, add it whole too and keep only worse.
  3. Return total; this is reached when everything fitted.

Code

#include <numeric>
#include <random>
#include <vector>
using namespace std;

class Solution {
public:
    double fractionalKnapsack(vector<int>& val, vector<int>& wt, int capacity) {
        vector<int> items(val.size());
        iota(items.begin(), items.end(), 0);
        mt19937 rng(12345);
        double total = 0;
        long long cap = capacity;
        while (!items.empty() && cap > 0) {
            int p = items[rng() % items.size()];  // a random pivot keeps the expected work linear
            vector<int> better, worse;
            long long wBetter = 0, wSame = 0, vBetter = 0, vSame = 0;
            for (int i : items) {
                long long l = (long long)val[i] * wt[p], r = (long long)val[p] * wt[i];
                if (l > r) { better.push_back(i); wBetter += wt[i]; vBetter += val[i]; }
                else if (l == r) { wSame += wt[i]; vSame += val[i]; }
                else worse.push_back(i);
            }
            if (wBetter >= cap) {  // the bag fills up inside the better group
                items.swap(better);
                continue;
            }
            total += vBetter;  // every better item goes in whole
            cap -= wBetter;
            if (wSame >= cap) return total + (double)val[p] * cap / wt[p];  // the pivot's group fills the rest
            total += vSame;
            cap -= wSame;
            items.swap(worse);
        }
        return total;
    }
};
import random


class Solution:
    def fractionalKnapsack(self, val, wt, capacity):
        items = list(range(len(val)))
        total = 0.0
        while items and capacity > 0:
            p = random.choice(items)  # a random pivot keeps the expected work linear
            better = [i for i in items if val[i] * wt[p] > val[p] * wt[i]]
            same = [i for i in items if val[i] * wt[p] == val[p] * wt[i]]
            worse = [i for i in items if val[i] * wt[p] < val[p] * wt[i]]
            w_better = sum(wt[i] for i in better)
            if w_better >= capacity:  # the bag fills up inside the better group
                items = better
                continue
            total += sum(val[i] for i in better)  # every better item goes in whole
            capacity -= w_better
            w_same = sum(wt[i] for i in same)
            if w_same >= capacity:  # the pivot's group fills the rest; all of it has the pivot's value per kilogram
                return total + val[p] * capacity / wt[p]
            total += sum(val[i] for i in same)
            capacity -= w_same
            items = worse
        return total
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

class Solution {
    public double fractionalKnapsack(int[] val, int[] wt, int capacity) {
        List<Integer> items = new ArrayList<>();
        for (int i = 0; i < val.length; i++) items.add(i);
        Random rng = new Random(12345);
        double total = 0;
        long cap = capacity;
        while (!items.isEmpty() && cap > 0) {
            int p = items.get(rng.nextInt(items.size()));  // a random pivot keeps the expected work linear
            List<Integer> better = new ArrayList<>(), worse = new ArrayList<>();
            long wBetter = 0, wSame = 0, vBetter = 0, vSame = 0;
            for (int i : items) {
                long l = (long) val[i] * wt[p], r = (long) val[p] * wt[i];
                if (l > r) { better.add(i); wBetter += wt[i]; vBetter += val[i]; }
                else if (l == r) { wSame += wt[i]; vSame += val[i]; }
                else worse.add(i);
            }
            if (wBetter >= cap) {  // the bag fills up inside the better group
                items = better;
                continue;
            }
            total += vBetter;  // every better item goes in whole
            cap -= wBetter;
            if (wSame >= cap) return total + (double) val[p] * cap / wt[p];  // the pivot's group fills the rest
            total += vSame;
            cap -= wSame;
            items = worse;
        }
        return total;
    }
}

Complexity Analysis

Time Complexity: O(n) expected. Each round reads the remaining items once, and the group left shrinks by a steady fraction on average.

Space Complexity: O(n) for the groups.

Measured. In C++, where both methods run as compiled code, the pivot method is far faster: 0.007 s against 0.071 s for sorting at 10^6 items, and 0.073 s against 0.974 s at 10^7. In Python the two are about equal at 10^5 items, 0.027 s against 0.026 s, because Python's sort runs as compiled code while the pivot method's loops run line by line; at 10^6 items the pivot method is a little ahead, 0.30 s against 0.36 s. At this problem's limits either one is fine; the pivot method is the one whose cost grows only in step with n.