Matrix Chain Multiplication (MCM)
Given a sequence of matrices, find the most efficient way to multiply these matrices together. The problem is not actually to perform the multiplications, but merely to decide the optimal order (parenthesization) to perform the multiplications to minimize the total number of scalar multiplications.
Examples
Example 1
- Dimensions Array (p):
[40, 20, 30, 10, 30] - Matrices:
A1of size40 x 20A2of size20 x 30A3of size30 x 10A4of size10 x 30
- Output:
26000 - Explanation: There are 4 matrices of dimensions
40 x 20,20 x 30,30 x 10,10 x 30.- The multiplication of
A1 x (A2 x A3) x A4requires:A2 x A3takes20 x 30 x 10 = 6000operations.A1 x (A2 A3)takes40 x 20 x 10 = 8000operations.(A1 (A2 A3)) x A4takes40 x 10 x 30 = 12000operations.- Total:
6000 + 8000 + 12000 = 26000operations.
- The multiplication of
Mathematical Substructure
Let dp[i][j] represent the minimum number of scalar multiplications needed to compute the matrix chain product A_i x A_{i+1} x ... x A_j.
For any split point k (where i <= k < j), we divide the chain into two subchains: A_i...A_k and A_{k+1}...A_j.
dp[i][j] = min( dp[i][k] + dp[k+1][j] + p[i-1] * p[k] * p[j] ) for all i <= k < j
Base Case
dp[i][i] = 0for all1 <= i <= n(A single matrix requires 0 multiplications).
1. Recursive Solution (Naive)
This approach has an exponential time complexity of O(2^N).
function matrixChainRecursive(p, i, j) {
if (i === j) return 0;
let min = Infinity;
// Place parenthesis at all possible positions between i and j
for (let k = i; k < j; k++) {
const count = matrixChainRecursive(p, i, k) +
matrixChainRecursive(p, k + 1, j) +
p[i - 1] * p[k] * p[j];
if (count < min) {
min = count;
}
}
return min;
}2. Memoization Solution (Top-Down DP)
function matrixChainMemoization(p) {
const n = p.length;
const memo = Array.from({ length: n }, () => new Array(n).fill(-1));
function helper(i, j) {
if (i === j) return 0;
if (memo[i][j] !== -1) return memo[i][j];
let min = Infinity;
for (let k = i; k < j; k++) {
const count = helper(i, k) +
helper(k + 1, j) +
p[i - 1] * p[k] * p[j];
if (count < min) {
min = count;
}
}
memo[i][j] = min;
return min;
}
return helper(1, n - 1);
}3. Tabulation Solution (Bottom-Up DP)
We construct a 2D table dp of size n x n and fill it based on subchain length (gap L).
function matrixChainTabulation(p) {
const n = p.length;
// dp[i][j] is the minimum number of multiplications to compute matrix A_i...j
const dp = Array.from({ length: n }, () => new Array(n).fill(0));
// L is chain length. L = 1 means single matrix, which has 0 multiplications (already 0)
for (let L = 2; L < n; L++) {
for (let i = 1; i < n - L + 1; i++) {
let j = i + L - 1;
dp[i][j] = Infinity;
for (let k = i; k < j; k++) {
// q = cost/scalar multiplications
const q = dp[i][k] + dp[k + 1][j] + p[i - 1] * p[k] * p[j];
if (q < dp[i][j]) {
dp[i][j] = q;
}
}
}
}
return dp[1][n - 1];
}
// Driver code
const p = [40, 20, 30, 10, 30];
console.log("Minimum multiplications required:", matrixChainTabulation(p));
// Output: Minimum multiplications required: 26000Complexity Analysis
- Time Complexity: O(N^3)
- There are $N^2$ states in the DP table, and for each state, we iterate up to $N$ times to find the optimal split point $k$.
- Space Complexity: O(N^2)
- To store the 2D DP table.