Problem
Given a sequence of matrices, find the most efficient way to multiply these matrices together. The problem is not to perform the multiplications, but to decide the order in which to multiply the matrices to minimize the total number of scalar multiplications.
Matrix multiplication is associative, so the result does not depend on how the matrices are parenthesized, but the number of operations does. Given an array p[]
of length n+1
, where the i
th matrix has dimensions p[i-1] x p[i]
, write a function MatrixChainOrder()
that returns the minimum number of multiplications needed to multiply the chain.
Examples
Example 1
|
|
Example 2
|
|
Example 3
|
|
Similar Problems
Printing brackets in Matrix Chain Multiplication
Method 1 - Naive
Intuition
The naive approach tries every possible way to parenthesize the product and recursively calculates the cost for each way. It chooses the minimum among all possible parenthesizations.
Approach
- For a chain of matrices from index
i
toj
, try placing a parenthesis at every possible positionk
betweeni
andj
. - Recursively compute the minimum cost for multiplying the left and right subchains.
- Add the cost of multiplying the two resulting matrices.
- Base case: if there is only one matrix (
i == j
), no multiplication is needed.
Code
|
|
|
|
|
|
Complexity
- ⏰ Time complexity:
O(2^n)
(exponential, as all possible parenthesizations are tried) - 🧺 Space complexity:
O(n)
(due to recursion stack)
Method 2 - Top Down DP
Intuition
The naive approach recalculates the same subproblems multiple times. Top-down DP (memoization) stores the results of subproblems in a table, so each subproblem is solved only once.
Optimal Substructure
The Matrix Chain Multiplication problem can be broken down into smaller subproblems. For a sequence of matrices, the goal is to determine the most efficient way to multiply them by placing parentheses in different positions. For a chain of n
matrices (A, B, C, D), you can split the multiplication at any position, resulting in subchains like:
- (A) × (BCD)
- (AB) × (CD)
- (ABC) × (D)
Each split divides the problem into two smaller matrix chain multiplication problems. The minimum number of scalar multiplications needed for the whole chain is the minimum among all possible splits, where each split’s cost is the sum of the costs of multiplying the left and right subchains plus the cost of multiplying the resulting two matrices. This recursive structure demonstrates the optimal substructure property.
Example
A (10×20), B (20×30), C (30×40), D (40×30). So, p = [10, 20, 30, 40, 30]
We want to compute the minimum number of scalar multiplications needed to multiply A × B × C × D.
graph TD A1["matrixChain(1,4)"] B1["matrixChain(1,1)"] B2["matrixChain(2,4)"] C1["matrixChain(2,2)"] C2["matrixChain(3,4)"] D1["matrixChain(3,3)"] D2["matrixChain(4,4)"] C3["matrixChain(2,3)"] D3["matrixChain(2,2)"] D4["matrixChain(3,3)"] B3["matrixChain(1,2)"] C4["matrixChain(1,1)"] C5["matrixChain(2,2)"] B4["matrixChain(1,3)"] C6["matrixChain(1,1)"] C7["matrixChain(2,3)"] D5["matrixChain(2,2)"] D6["matrixChain(3,3)"] A1 --> B1 A1 --> B2 B2 --> C1 B2 --> C2 C2 --> D1 C2 --> D2 B2 --> C3 C3 --> D3 C3 --> D4 A1 --> B3 B3 --> C4 B3 --> C5 A1 --> B4 B4 --> C6 B4 --> C7 C7 --> D5 C7 --> D6
Suppose you have matrices A, B, C, D. To find the minimum multiplication cost, you can split the chain at different positions:
- First split: (A) × (BCD)
- Second split: (AB) × (CD)
- Third split: (ABC) × (D)
Each split leads to recursive calls for the left and right segments. For instance, the subproblem of multiplying (B × C × D) may be encountered in both the first and second splits, leading to redundant calculations if not stored and reused.
Approach
- Use a 2D memoization table
dp[i][j]
to store the minimum cost for multiplying matrices fromi
toj
. - If the value is already computed, return it.
- Otherwise, compute recursively as in the naive approach, but store and reuse results.
Code
|
|
|
|
|
|
Complexity
- ⏰ Time complexity:
O(n^3)
(since there are O(n^2) subproblems and each takes O(n) time) - 🧺 Space complexity:
O(n^2)
(for the memoization table)
Method 3 - Bottom up DP
Intuition
Bottom-up DP builds the solution iteratively by solving smaller subproblems first and using their results to solve larger subproblems. We fill a DP table where dp[i][j]
stores the minimum cost to multiply matrices from i
to j
.
Approach
- Initialize a 2D table
dp
of sizen x n
with zeros on the diagonal (cost of multiplying one matrix is zero). - For chain lengths from 2 to
n-1
, compute the minimum cost for all subchains. - For each subchain
(i, j)
, try all possible splitsk
and updatedp[i][j]
with the minimum cost. - The answer is in
dp[1][n-1]
.
Code
|
|
|
|
|
|
Complexity
- ⏰ Time complexity:
O(n^3)
- 🧺 Space complexity:
O(n^2)
Dry Run
Let’s fill the DP table for this example (p = [10, 20, 30, 40, 30]
, n = 5):
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
0 | 0 | ||||
1 | 0 | ||||
2 | 0 | ||||
3 | 0 | ||||
4 | 0 |
Now, fill for chain length 2:
dp[1][2]
= 10×20×30 = 6000dp[2][3]
= 20×30×40 = 24000dp[3][4]
= 30×40×30 = 36000
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
0 | 0 | ||||
1 | 0 | 6000 | |||
2 | 0 | 24000 | |||
3 | 0 | 36000 | |||
4 | 0 |
Chain length 3:
-
dp[1][3]
= min( (10×20×40 + 0 + 24000) = 32000, (6000 + 10×30×40 + 0) = 18000 ) = 18000 -
dp[2][4]
= min( (20×30×30 + 0 + 36000) = 54000, (24000 + 20×40×30 + 0) = 48000 ) = 48000
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
0 | 0 | ||||
1 | 0 | 6000 | 18000 | ||
2 | 0 | 24000 | 48000 | ||
3 | 0 | 36000 | |||
4 | 0 |
Chain length 4:
dp[1][4]
= min( (10×20×30 + 0 + 48000) = 54000, (6000 + 10×30×30 + 36000) = 51000, (18000 + 10×40×30 + 0) = 30000 ) = 30000
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
0 | 0 | ||||
1 | 0 | 6000 | 18000 | 30000 | |
2 | 0 | 24000 | 48000 | ||
3 | 0 | 36000 | |||
4 | 0 |
Final answer:
Minimum number of multiplications = 30000 (dp[1][4]
)