Skip to content
dsa450for five year olds

Greedy · 5 of 35

Fractional Knapsack Problem

Fill a bag that can only carry so much by taking the most valuable stuff per kilo first, cutting the last piece to fit.

Simple way
time O(n! * n)
room O(n)
Clever way
time O(n log n)
room O(n)

The problem, simply

You get n items. Each item has a weight and a value. Your bag can carry at most W kilos. You may cut an item and take only part of it: half the weight gives half the value.

Give back the largest total value you can put in the bag.

A story to remember it

Picture it

arrayitems (weight, value)
0123A 10, 60B 40, 40C 20, 100D 30, 120
stackbag, 50 kg
empty

Four items, written as name, weight and value. The bag (a pile, bottom first) holds 50 kilos and starts empty.

Examples

A normal case

In
weights = [10, 40, 20, 30], values = [60, 40, 100, 120], W = 50
Out
240.0

Value per kilo is 6, 1, 5 and 4. Take A whole (60), C whole (100), then 20 of D's 30 kilos (80).

The textbook set

In
weights = [10, 20, 30], values = [60, 100, 120], W = 50
Out
240.0

Same idea: 60 + 100 + two thirds of 120.

Everything fits

In
weights = [5, 5], values = [10, 20], W = 100
Out
30.0

The bag is big enough for all of it. Take everything.

Only a slice of one item

In
weights = [40], values = [100], W = 10
Out
25.0

A quarter of the item fits, so a quarter of its value.

Way 1: the simple way

Some order of filling must be the best one. So try every order. For each order, put items in one by one, whole if they fit, and cut the first one that does not fit. Remember the best total.

  1. List every order of the n items.
  2. For each order, fill the bag and cut the last piece.
  3. Keep the biggest value.
java
class Solution {
    private double best = 0;

    public double fractionalKnapsack(int[] weight, int[] value, int W) {
        best = 0;
        int n = weight.length;
        int[] order = new int[n];
        for (int i = 0; i < n; i++) order[i] = i;
        permute(order, 0, weight, value, W);
        return best;
    }

    private void permute(int[] order, int k, int[] wt, int[] val, int W) {
        if (k == order.length) {
            best = Math.max(best, fill(order, wt, val, W));
            return;
        }
        for (int i = k; i < order.length; i++) {
            swap(order, k, i);
            permute(order, k + 1, wt, val, W);
            swap(order, k, i);
        }
    }

    private double fill(int[] order, int[] wt, int[] val, int W) {
        double room = W, total = 0;
        for (int i : order) {
            if (room == 0) break;
            double take = Math.min(room, wt[i]);   // whole item, or the part that fits
            total += take / wt[i] * val[i];
            room -= take;
        }
        return total;
    }

    private void swap(int[] a, int i, int j) { int t = a[i]; a[i] = a[j]; a[j] = t; }
}

How long it takes: n! orders, each filled in O(n), O(n! * n). Extra room: the order array and the recursion, O(n).

Way 2: the clever way

Only one order matters: best value per kilo first.

  1. Work out value / weight for every item.
  2. Sort the items by that number, biggest first.
  3. Walk the sorted items. If the whole item fits, take it. If not, take the part that fits and stop.

Why does this work? Suppose your bag has a kilo of something worth less per kilo while a better item is still not fully in. Swap that kilo for a kilo of the better item. The weight stays the same and the value goes up. So the best bag never has such a kilo, and that is exactly the bag the greedy builds.

Best value per kilo goes in first

01 / 06

arrayitems by value per kilo
0123A 6C 5D 4B 1i
stackbag
empty

Work out value per kilo for each item and sort, best first. A gives 6 per kilo, C 5, D 4, B only 1. The bag holds 50 kilos and is empty.

room left
50
value
0
  • looking now
  • done
  • answer
  • rejected
  • just added
  • ignored
java
class Solution {
    public double fractionalKnapsack(int[] weight, int[] value, int W) {
        int n = weight.length;
        Integer[] order = new Integer[n];
        for (int i = 0; i < n; i++) order[i] = i;
        // best value per kilo first: compare v[a]/w[a] with v[b]/w[b] by cross multiplying
        Arrays.sort(order, (a, b) -> Long.compare((long) value[b] * weight[a],
                                                  (long) value[a] * weight[b]));

        double total = 0;
        long room = W;
        for (int i : order) {
            if (room == 0) break;
            if (weight[i] <= room) {            // the whole item fits
                total += value[i];
                room -= weight[i];
            } else {                             // cut the part that fits
                total += (double) value[i] * room / weight[i];
                room = 0;
            }
        }
        return total;
    }
}

How long it takes: the sort is O(n log n) and the walk is O(n), so O(n log n). Extra room: the order array, O(n).

Watch out for

  • Integer division. value / weight on two ints rounds down, so 5/2 and 2/1 look the same. Use doubles, or compare by cross multiplying as in the code.
  • Cross multiplying can overflow int. Cast to long first.
  • An item with weight 0 has an endless value per kilo. Take it for free before sorting, or the division breaks.
  • Return a double. Many judges want it printed with a fixed number of decimals.

What interviewers ask next

  • Can you beat O(n log n)? Yes, O(n) on average. Use quickselect to find the item where the bag runs out, without sorting everything.
  • What changes for 0/1 knapsack? Greedy no longer works. Use dynamic programming over capacity, O(n * W).
  • How is this related to linear programming? Fractional knapsack is a tiny linear program, and the greedy is its exact solution.
  • What if there are many bags? With cuts allowed it is still easy: treat all bags as one big bag of the total size and fill by value per kilo.

Remember this

Grown-up words: the bag is the knapsack, value per kilo is the value to weight ratio, cutting items makes this the fractional knapsack, and the swap a kilo argument is an exchange argument.