Skip to content
dsa450for five year olds

Dynamic Programming · 6 of 60

Matrix Chain Multiplication

Choose where to put the brackets when multiplying a chain of matrices so the total number of small multiplications is as low as possible.

Simple way
time O(3^n)
room O(n)
Clever way
time O(n^3)
room O(n^2)

The problem, simply

You have a chain of matrices to multiply, like A x B x C x D. You may not change their order, but you may choose which pair to multiply first. That choice is where the brackets go: (AB)(CD) or ((AB)C)D or (A(BC))D and so on.

Multiplying a p x q matrix by a q x r matrix costs p * q * r small multiplications. The sizes come as one array d: matrix i is d[i - 1] x d[i]. Give back the smallest total cost.

A story to remember it

Picture it

arraysizes d
012344020301030

Four matrices in a chain. A is 40x20, B is 20x30, C is 30x10, D is 10x30. Neighbors share a size, which is why they can be multiplied. The order of the chain must stay the same; only the brackets change.

  • checking

Examples

A normal case

In
d = [40, 20, 30, 10, 30]
Out
26000

The best order is (A(BC))D: BC costs 6000, then A times that costs 8000, then times D costs 12000. 6000 + 8000 + 12000 = 26000.

Order matters a lot

In
d = [10, 30, 5, 60]
Out
4500

(AB)C costs 10 x 30 x 5 + 10 x 5 x 60 = 1500 + 3000 = 4500. A(BC) costs 30 x 5 x 60 + 10 x 30 x 60 = 9000 + 18000 = 27000, six times more.

Two matrices

In
d = [10, 20, 30]
Out
6000

Only one way: 10 x 20 x 30.

One matrix

In
d = [10, 20]
Out
0

Nothing to multiply.

Way 1: the simple way

Let cost(i, j) be the cheapest way to multiply matrices i to j.

  1. If i == j, it is one matrix: 0.
  2. Otherwise, try every split k from i to j - 1. The last multiplication joins i..k with k+1..j, and costs d[i - 1] * d[k] * d[j].
  3. The answer is the smallest cost(i, k) + cost(k + 1, j) + d[i - 1] * d[k] * d[j].
tree
ABCDABCDBCDCDBCBCDABABCDABCABCABCDagainagainagain

The calls for ABCD. Each split makes two calls, one for the left part and one for the right part. Single matrices are free (faded). Parts marked again were already solved on another branch, yet the plain recursion solves them again.

  • rejected
  • ignored
java
class Solution {
    // matrices are numbered 1 to n, matrix i is d[i - 1] x d[i]
    public int cost(int[] d, int i, int j) {
        if (i == j) return 0;                         // one matrix is free
        int best = Integer.MAX_VALUE;
        for (int k = i; k < j; k++) {                 // the last join is after k
            int c = cost(d, i, k) + cost(d, k + 1, j) + d[i - 1] * d[k] * d[j];
            best = Math.min(best, c);
        }
        return best;
    }

    public int matrixChainOrder(int[] d) {
        return cost(d, 1, d.length - 1);
    }
}

How long it takes: a chain of n makes calls on every shorter left and right part, and the count of calls grows about three times for each extra matrix, O(3^n). Extra room: the call chain is at most n deep, O(n).

Way 2: the clever way

There are only about n^2 / 2 parts i..j. Keep each part's best cost in a table dp[i][j]. A part only needs shorter parts, so fill the table by chain length: first all pairs, then all triples, and so on.

  1. dp[i][i] = 0 for every matrix.
  2. For length 2 up to n, for every start i with j = i + len - 1:
  3. Try every split k and keep the smallest dp[i][k] + dp[k + 1][j] + d[i - 1] * d[k] * d[j].
  4. The answer is dp[1][n], the top right box.

In the table, dp[i][k] sits in the same row to the left, and dp[k + 1][j] sits in the same column below.

Fill the cost table from short chains to long ones

01 / 10

grid
ABCDABCD0000

Box (i, j) will hold the cheapest cost to multiply the matrices from i to j. One matrix alone costs nothing, so the diagonal is 0.

  • looking now
  • checking
  • done
  • answer
  • just added
  • ignored
java
class Solution {
    public int matrixChainOrder(int[] d) {
        int n = d.length - 1;                       // number of matrices
        int[][] dp = new int[n + 1][n + 1];         // dp[i][i] = 0 already

        for (int len = 2; len <= n; len++) {        // short chains first
            for (int i = 1; i + len - 1 <= n; i++) {
                int j = i + len - 1;
                dp[i][j] = Integer.MAX_VALUE;
                for (int k = i; k < j; k++) {       // the last join is after k
                    int c = dp[i][k] + dp[k + 1][j] + d[i - 1] * d[k] * d[j];
                    if (c < dp[i][j]) dp[i][j] = c;
                }
            }
        }
        return dp[1][n];
    }
}

How long it takes: about n^2 / 2 boxes and up to n splits for each, O(n^3). Extra room: the table, O(n^2).

Watch out for

  • Indexing: with n matrices the array d has n + 1 numbers. Matrix i is d[i - 1] x d[i].
  • The join cost uses the outer sizes of the two parts, d[i - 1] * d[k] * d[j], not the sizes of single neighbors.
  • Costs can overflow int for big sizes. Use long if sizes reach the thousands.
  • Greedy (always multiply the cheapest pair first) is not safe. Try it on [40, 20, 30, 10, 30].

What interviewers ask next

  • Print the brackets. Store the best k in split[i][j], then print recursively: ( + print(i, k) + print(k + 1, j) + ). For the example it gives ((A(BC))D).
  • Why "the last join"? Every bracketing has exactly one last multiplication, and it splits the chain into a left part and a right part that are solved separately. That is the interval DP idea.
  • Same pattern elsewhere: burst balloons, minimum cost to merge stones, optimal binary search tree, boolean parenthesization, minimum score triangulation of a polygon.
  • Can it be faster? Hu and Shing found an O(n log n) method for this exact problem, but interviews expect O(n^3).

Remember this

Grown-up words: the train pieces are matrices, the stickers are scalar multiplications, choosing brackets is the parenthesization, the chart over (start, end) is interval dynamic programming, and the repeated parts are overlapping subproblems.