The cheapest order to multiply matrices
A chain of matrices is described by dims: matrix i has dims[i] rows and
dims[i + 1] columns, so there are dims.length − 1 matrices. Multiplying a p × q matrix by a q × r
matrix costs p · q · r scalar multiplications. Return the minimum total cost of computing the product of the whole chain,
over all ways of placing the brackets.
Examples
| Input | Output | Why |
|---|---|---|
dims = [10,30,5,60] | 4500 | (A·B)·C: 1,500 + 3,000. |
dims = [40,20,30,10,30] | 26000 | (A·(B·C))·D. |
dims = [10,20] | 0 | A single matrix. |
Constraints
- 2 ≤ dims.length ≤ 200
- 1 ≤ dims[i] ≤ 500
Hints
Hint 1
Some multiplication is done last. It splits the chain i..j into i..k and k + 1..j.
Hint 2
cost(i, j) = min over k of cost(i, k) + cost(k + 1, j) + dims[i] · dims[k + 1] · dims[j + 1].
Hint 3
Intervals read shorter intervals: fill by increasing length.
Where people lose marks
- Off-by-one between matrix indices and dims indices: matrix k is dims[k] × dims[k + 1].
- Filling the table row by row from the top, before shorter intervals in later rows exist.
- Overflow: costs reach about 200 · 500³ = 2.5 · 10¹⁰, beyond 32 bits.
Dry run on [40,20,30,10,30]
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 3from 000240001·2·3from 100160002·3from 2010290003from 301203
Length 2: matrices 0..1: best split after 0, cost 24000; matrices 1..2: best split after 1, cost 6000; matrices 2..3: best split after 2, cost 9000.
-
Step 2 / 3from 000240001140002·3from 100160002120003from 2010290003from 301203
Length 3: matrices 0..2: best split after 0, cost 14000; matrices 1..3: best split after 2, cost 12000.
-
Step 3 / 3from 000240001140002260003from 100160002120003from 2010290003from 301203
Length 4: matrices 0..3: best split after 2, cost 26000. The answer is 26000.
How to think about it
Interval DP with a split point. The last multiplication in computing matrices i..j joins the product of i..k and the product of k + 1..j, for some k; the two results are dims[i] × dims[k + 1] and dims[k + 1] × dims[j + 1], so joining them costs dims[i] · dims[k + 1] · dims[j + 1]. Trying every k and taking the cheapest gives the recurrence; single matrices cost 0. Filling intervals by increasing length guarantees both halves are ready.
Complexity. Time O(n³) · Space O(n²)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def matrix_chain(dims):
n = len(dims) - 1 # number of matrices
cost = [[0] * n for _ in range(n)] # cost[i][j]: matrices i..j
for length in range(2, n + 1):
for i in range(n - length + 1):
j = i + length - 1
cost[i][j] = min(cost[i][k] + cost[k + 1][j] + dims[i] * dims[k + 1] * dims[j + 1]
for k in range(i, j)) # k: where the last multiplication splits
return cost[0][n - 1]#include <algorithm>
#include <climits>
#include <vector>
long long solve(const std::vector<int>& dims) {
const int n = dims.size() - 1;
std::vector<std::vector<long long>> cost(n, std::vector<long long>(n, 0));
for (int len = 2; len <= n; ++len)
for (int i = 0; i + len - 1 < n; ++i) {
int j = i + len - 1;
cost[i][j] = LLONG_MAX;
for (int k = i; k < j; ++k) // the last split
cost[i][j] = std::min(cost[i][j], cost[i][k] + cost[k + 1][j] + (long long)dims[i] * dims[k + 1] * dims[j + 1]);
}
return cost[0][n - 1];
}class Solution {
public long matrixChain(int[] dims) {
int n = dims.length - 1;
long[][] cost = new long[n][n];
for (int len = 2; len <= n; len++)
for (int i = 0; i + len - 1 < n; i++) {
int j = i + len - 1;
cost[i][j] = Long.MAX_VALUE;
for (int k = i; k < j; k++) // the last split
cost[i][j] = Math.min(cost[i][j], cost[i][k] + cost[k + 1][j] + (long) dims[i] * dims[k + 1] * dims[j + 1]);
}
return cost[0][n - 1];
}
}function matrixChain(dims) {
const n = dims.length - 1;
const cost = Array.from({ length: n }, () => new Array(n).fill(0));
for (let len = 2; len <= n; len++)
for (let i = 0; i + len - 1 < n; i++) {
const j = i + len - 1;
cost[i][j] = Infinity;
for (let k = i; k < j; k++) // the last split
cost[i][j] = Math.min(cost[i][j], cost[i][k] + cost[k + 1][j] + dims[i] * dims[k + 1] * dims[j + 1]);
}
return cost[0][n - 1];
}fn solve(dims: &[i64]) -> i64 {
let n = dims.len() - 1;
let mut cost = vec![vec![0i64; n]; n];
for len in 2..=n {
for i in 0..=n - len {
let j = i + len - 1;
cost[i][j] = (i..j) // k: the last split
.map(|k| cost[i][k] + cost[k + 1][j] + dims[i] * dims[k + 1] * dims[j + 1])
.min()
.unwrap();
}
}
cost[0][n - 1]
}import Data.Array
solve :: [Int] -> Int
solve dims = cost ! (0, n - 1)
where
n = length dims - 1
d = listArray (0, n) dims
-- a lazy table: every interval refers only to shorter ones
cost = array ((0, 0), (n - 1, n - 1)) [((i, j), go i j) | i <- [0 .. n - 1], j <- [0 .. n - 1]] :: Array (Int, Int) Int
go i j
| i >= j = 0
| otherwise = minimum [cost ! (i, k) + cost ! (k + 1, j) + d ! i * d ! (k + 1) * d ! (j + 1) | k <- [i .. j - 1]]
Variants to try
- Print the optimal bracketing by recording the best split per interval.
- Burst balloons: the same shape with "the last balloon to burst" as the split.
- Optimal binary search tree for keys with access frequencies.
Next problems
The cheapest way up a staircase
Each step has a cost; climb one or two steps at a time; start on step 0 or 1. Return the cheapest way past the top.
Open the problem → Medium15 minCan a subset reach the target?
Decide whether some subset of positive integers adds up to exactly the target. A boolean array over totals, filled downwards for each number.
Open the problem → Medium15 minCount the palindromic substrings
Count every contiguous substring that reads the same backwards, counting different positions separately. Each successful expansion step is one more.
Open the problem →Write a solution and run it against the real test table.
Keep going
The cheapest way up a staircase
Each step has a cost; climb one or two steps at a time; start on step 0 or 1. Return the cheapest way past the top.
Open the problem → Medium15 minCan a subset reach the target?
Decide whether some subset of positive integers adds up to exactly the target. A boolean array over totals, filled downwards for each number.
Open the problem → Medium15 minCount the palindromic substrings
Count every contiguous substring that reads the same backwards, counting different positions separately. Each successful expansion step is one more.
Open the problem → Dynamic programmingThe pattern behind it
Write the recursion, notice the repeats, fill a table in an order that makes each entry final.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.