Given a chain of matrices A1, A2, A3,.....An, you have to figure out the most efficient way to multiply these matrices. In other words, determine where to place parentheses to minimize the number of multiplications.
Given an array nums of size n. Dimension of matrix Ai ( 0 < i < n ) is nums[i - 1] x nums[i].Find a minimum number of multiplications needed to multiply the chain.
Input : nums = [10, 15, 20, 25]
Output : 8000
Explanation : There are two ways to multiply the chain - A1*(A2*A3) or (A1*A2)*A3.
If we multiply in order- A1*(A2*A3), then number of multiplications required are 11250.
If we multiply in order- (A1*A2)*A3, then number of multiplications required are 8000.
Thus minimum number of multiplications required is 8000.
Input : nums = [4, 2, 3]
Output : 24
Explanation : There is only one way to multiply the chain - A1*A2.
Thus minimum number of multiplications required is 24.
Input : nums = [1, 2, 3, 4, 5]
Whenever we need to find the answer to a large problem such that the problem can be broken into subproblems and the final answer varies due to the order in which the subproblems are solved, we can think in terms of partition DP.
Let us first understand the problem of matrix chain multiplication. In order to understand the problem we need to first understand the rules of matrix multiplication:
As this problem of matrix multiplication solves one big problem ( minimum operations to get A1.A2….An) and the answer varies in the order the subproblems are being solved, we can identify this problem of pattern partition DP.
As we can see 3 partitions are possible, to try all three partitions, initialize a for loop starting from 'i' and ending at (j-1), (1 to 3) in this case. The two partitions will be f(i,k) and f(k+1,j).
As there are only two matrices, we can say that only one partition is possible at k=1, when we do this partition, we see that the two partitions made are f(1,1) and f(2,2) (by f(i,k) and f(k+1,j) therefore we hit the base condition in both of these subcases which return 0.
Now, we know that the subproblems/partitions give us 0 operations, but we still have some work to do. The partition f(i,k) gives us a resultant matrix of dimensions [i -1 x k] and the partition f(k+1,j) gives us a resultant matrix of dimensions [k,j]. Therefore we need to count the number of operations for our two partitions (k=1) as shown:
Now, this is at one partition, in general, we need to calculate this answer of every partition and return the minimum as the answer.
#include <bits/stdc++.h>
using namespace std;
class Solution{
private:
/* Recursive function to compute the
minimum cost of matrix multiplication*/
int func(vector<int>& arr, int i, int j) {
/* Base case: When there is only one
matrix, no multiplication is needed*/
if (i == j)
return 0;
int mini = INT_MAX;
// Partition the matrices between i and j
for (int k = i; k <= j - 1; k++) {
/* Compute the cost of multiplying matrices
from i to k and from k+1 to j and add cost
of multiplying the two resulting matrices*/
int ans = func(arr, i, k) + func(arr, k + 1, j) + arr[i - 1] * arr[k] * arr[j];
// Update the minimum cost
mini = min(mini, ans);
}
// Return the minimum cost found
return mini;
}
public:
/* Function to set up the parameters
and call the recursive function*/
int matrixMultiplication(vector<int>& nums) {
int N = nums.size();
// Starting index of the matrix chain
int i = 1;
// Ending index of the matrix chain
int j = N - 1;
// Call the recursive function
return func(nums, i, j);
}
};
int main() {
vector<int> arr = {10, 20, 30, 40, 50};
//Create an instance of Solution class
Solution sol;
// Print the result
cout << "The minimum number of operations is " << sol.matrixMultiplication(arr);
return 0;
}
import java.util.*;
class Solution {
/* Recursive function to compute the
minimum cost of matrix multiplication*/
private int func(int[] arr, int i, int j) {
/* Base case: When there is only one
matrix, no multiplication is needed*/
if (i == j)
return 0;
int mini = Integer.MAX_VALUE;
// Partition the matrices between i and j
for (int k = i; k <= j - 1; k++) {
/* Compute the cost of multiplying matrices
from i to k and from k+1 to j and add cost
of multiplying the two resulting matrices*/
int ans = func(arr, i, k) + func(arr, k + 1, j) + arr[i - 1] * arr[k] * arr[j];
// Update the minimum cost
mini = Math.min(mini, ans);
}
// Return the minimum cost found
return mini;
}
/* Function to set up the parameters
and call the recursive function*/
public int matrixMultiplication(int[] nums) {
int N = nums.length;
// Starting index of the matrix chain
int i = 1;
// Ending index of the matrix chain
int j = N - 1;
// Call the recursive function
return func(nums, i, j);
}
public static void main(String[] args) {
int[] arr = {10, 20, 30, 40, 50};
// Create an instance of Solution class
Solution sol = new Solution();
// Print the result
System.out.println("The minimum number of operations is " + sol.matrixMultiplication(arr));
}
}
class Solution:
""" Recursive function to compute the
minimum cost of matrix multiplication"""
def func(self, arr, i, j):
""" Base case: When there is only one
matrix, no multiplication is needed"""
if i == j:
return 0
mini = float('inf')
# Partition the matrices between i and j
for k in range(i, j):
""" Compute the cost of multiplying matrices
from i to k and from k+1 to j and add cost
of multiplying the two resulting matrices"""
ans = self.func(arr, i, k) + self.func(arr, k + 1, j) + arr[i - 1] * arr[k] * arr[j]
# Update the minimum cost
mini = min(mini, ans)
return mini
def matrixMultiplication(self, nums):
N = len(nums)
# Starting index of the matrix chain
i = 1
# Ending index of the matrix chain
j = N - 1
# Call the recursive function
return self.func(nums, i, j)
if __name__ == "__main__":
arr = [10, 20, 30, 40, 50]
# Create an instance of Solution class
sol = Solution()
# Print the result
print("The minimum number of operations is", sol.matrixMultiplication(arr))
class Solution {
/* Recursive function to compute the
minimum cost of matrix multiplication*/
func(arr, i, j) {
/* Base case: When there is only one
matrix, no multiplication is needed*/
if (i === j) return 0;
let mini = Infinity;
// Partition the matrices between i and j
for (let k = i; k <= j - 1; k++) {
/* Compute the cost of multiplying matrices
from i to k and from k+1 to j and add cost
of multiplying the two resulting matrices*/
const ans = this.func(arr, i, k) + this.func(arr, k + 1, j) + arr[i - 1] * arr[k] * arr[j];
// Update the minimum cost
mini = Math.min(mini, ans);
}
return mini;
}
/* Function to set up the parameters
and call the recursive function*/
matrixMultiplication(nums) {
const N = nums.length;
// Starting index of the matrix chain
const i = 1;
// Ending index of the matrix chain
const j = N - 1;
// Call the recursive function
return this.func(nums, i, j);
}
}
const arr = [10, 20, 30, 40, 50];
// Create an instance of Solution class
const sol = new Solution();
// Print the result
console.log(`The minimum number of operations is ${sol.matrixMultiplication(arr)}`);
If we draw the recursion tree, we will see that there are overlapping subproblems. Hence the DP approaches can be applied to the recursive solution.
In order to convert a recursive solution to memoization the following steps will be taken:The dp array stores the calculations of subproblems. Initialize dp array with -1, to indicate that no subproblem has been solved yet.
#include <bits/stdc++.h>
using namespace std;
class Solution{
private:
/* Recursive function to compute the
minimum cost of matrix multiplication*/
int func(vector<int>& arr, int i, int j, vector<vector<int>>& dp) {
/* Base case: When there is only one
matrix, no multiplication is needed*/
if (i == j)
return 0;
//Check if the subproblem is already calculated
if(dp[i][j]!=-1)
return dp[i][j];
int mini = INT_MAX;
// Partition the matrices between i and j
for (int k = i; k <= j - 1; k++) {
/* Compute the cost of multiplying matrices
from i to k and from k+1 to j and add cost
of multiplying the two resulting matrices*/
int ans = func(arr, i, k, dp) + func(arr, k + 1, j, dp) + arr[i - 1] * arr[k] * arr[j];
// Update the minimum cost
mini = min(mini, ans);
}
// Store and return the minimum cost found
return dp[i][j] = mini;
}
public:
/* Function to set up the parameters
and call the recursive function*/
int matrixMultiplication(vector<int>& nums) {
int N = nums.size();
// Starting index of the matrix chain
int i = 1;
// Ending index of the matrix chain
int j = N - 1;
vector<vector<int>> dp(N,vector<int>(N,-1));
// Call the recursive function
return func(nums, i, j, dp);
}
};
int main() {
vector<int> arr = {10, 20, 30, 40, 50};
//Create an instance of Solution class
Solution sol;
// Print the result
cout << "The minimum number of operations is " << sol.matrixMultiplication(arr);
return 0;
}
import java.util.*;
class Solution {
/* Recursive function to compute the
minimum cost of matrix multiplication*/
private int func(int[] arr, int i, int j, int[][] dp) {
/* Base case: When there is only one
matrix, no multiplication is needed*/
if (i == j)
return 0;
// Check if the subproblem is already calculated
if (dp[i][j] != -1)
return dp[i][j];
int mini = Integer.MAX_VALUE;
// Partition the matrices between i and j
for (int k = i; k <= j - 1; k++) {
/* Compute the cost of multiplying
matrices from i to k and from k+1 to j*/
int ans = func(arr, i, k, dp) + func(arr, k + 1, j, dp) + arr[i - 1] * arr[k] * arr[j];
// Update the minimum cost
mini = Math.min(mini, ans);
}
// Store and return the minimum cost found
dp[i][j] = mini;
return mini;
}
/* Function to set up the parameters
and call the recursive function*/
public int matrixMultiplication(int[] nums) {
int N = nums.length;
// Starting index of the matrix chain
int i = 1;
// Ending index of the matrix chain
int j = N - 1;
int[][] dp = new int[N][N];
for (int[] row : dp)
Arrays.fill(row, -1);
// Call the recursive function
return func(nums, i, j, dp);
}
public static void main(String[] args) {
int[] arr = {10, 20, 30, 40, 50};
// Create an instance of Solution class
Solution sol = new Solution();
// Print the result
System.out.println("The minimum number of operations is " + sol.matrixMultiplication(arr));
}
}
import sys
class Solution:
def __init__(self):
self.dp = None
""" Recursive function to compute the
minimum cost of matrix multiplication"""
def func(self, arr, i, j):
""" Base case: When there is only one
matrix, no multiplication is needed"""
if i == j:
return 0
# Check if the subproblem is already calculated
if self.dp[i][j] != -1:
return self.dp[i][j]
mini = float('inf')
# Partition the matrices between i and j
for k in range(i, j):
""" Compute the cost of multiplying
matrices from i to k and from k+1 to j"""
ans = self.func(arr, i, k) + self.func(arr, k + 1, j) + arr[i - 1] * arr[k] * arr[j]
# Update the minimum cost
mini = min(mini, ans)
# Store and return the minimum cost found
self.dp[i][j] = mini
return mini
""" Function to set up the parameters
and call the recursive function"""
def matrixMultiplication(self, arr):
N = len(arr)
# Starting index of the matrix chain
i = 1
# Ending index of the matrix chain
j = N - 1
self.dp = [[-1] * N for _ in range(N)]
# Call the recursive function
return self.func(arr, i, j)
if __name__ == "__main__":
arr = [10, 20, 30, 40, 50]
# Create an instance of Solution class
sol = Solution()
# Print the result
print("The minimum number of operations is", sol.matrixMultiplication(arr))
class Solution {
constructor() {
this.dp = null;
}
/* Recursive function to compute the
minimum cost of matrix multiplication*/
func(arr, i, j) {
/* Base case: When there is only one
matrix, no multiplication is needed*/
if (i === j) return 0;
// Check if the subproblem is already calculated
if (this.dp[i][j] !== -1) return this.dp[i][j];
let mini = Infinity;
// Partition the matrices between i and j
for (let k = i; k <= j - 1; k++) {
/* Compute the cost of multiplying
matrices from i to k and from k+1 to j*/
const ans = this.func(arr, i, k) + this.func(arr, k + 1, j) + arr[i - 1] * arr[k] * arr[j];
// Update the minimum cost
mini = Math.min(mini, ans);
}
// Store and return the minimum cost found
this.dp[i][j] = mini;
return mini;
}
/* Function to set up the parameters
and call the recursive function*/
matrixMultiplication(nums) {
const N = nums.length;
// Starting index of the matrix chain
const i = 1;
// Ending index of the matrix chain
const j = N - 1;
this.dp = Array.from({ length: N }, () => Array(N).fill(-1));
// Call the recursive function
return this.func(nums, i, j);
}
}
const arr = [10, 20, 30, 40, 50];
// Create an instance of Solution class
const sol = new Solution();
// Print the result
console.log(`The minimum number of operations is ${sol.matrixMultiplication(arr)}`);
The matrix chain multiplication problem aims to determine the optimal order of multiplying a given sequence of matrices to minimize the total number of scalar multiplications. The key insight is to utilize dynamic programming by breaking down the problem into smaller subproblems, solving each subproblem once, and storing the solutions. This method avoids redundant calculations and efficiently computes the minimum cost. The process involves considering every possible way to split the chain of matrices, calculating the cost for each split, and then choosing the split that results in the minimum cost.
dp
where dp[i][j]
represents the minimum cost of multiplying the chain of matrices from index i
to index j
.dp
array to 0, as a single matrix requires no multiplication.dp
array with the minimum cost found for each pair of starting and ending indices.dp[1][n-1]
, representing the minimum cost of multiplying the entire chain of matrices.#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
int matrixMultiplication(vector<int>& nums) {
int n = nums.size();
vector<vector<int>> dp(n, vector<int>(n, INT_MAX));
// A single matrix needs no multiplication cost
for (int i = 1; i < n; ++i) {
dp[i][i] = 0;
}
// Filling the dp array
for (int length = 2; length < n; ++length) { // length of the chain
for (int i = 1; i <= n - length; ++i) {
int j = i + length - 1;
for (int k = i; k < j; ++k) {
int cost = dp[i][k] + dp[k + 1][j] + nums[i - 1] * nums[k] * nums[j];
if (cost < dp[i][j]) {
dp[i][j] = cost;
}
}
}
}
// The result is in dp[1][n-1]
return dp[1][n - 1];
}
};
int main() {
Solution sol;
vector<int> nums = {10, 15, 20, 25};
// Output should be 8000
cout << sol.matrixMultiplication(nums) << endl;
return 0;
}
class Solution {
public int matrixMultiplication(int[] nums) {
int n = nums.length;
int[][] dp = new int[n][n];
// Initialize the dp array with large values
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
dp[i][j] = Integer.MAX_VALUE;
}
}
// A single matrix doesn't require any multiplication
for (int i = 1; i < n; i++) {
dp[i][i] = 0;
}
// Filling the dp array in a bottom-up manner
for (int length = 2; length < n; length++) {
for (int i = 1; i <= n - length; i++) {
int j = i + length - 1;
for (int k = i; k < j; k++) {
// Calculate cost
int cost = dp[i][k] + dp[k + 1][j] + nums[i - 1] * nums[k] * nums[j];
// Take the minimum cost
if (cost < dp[i][j]) {
dp[i][j] = cost;
}
}
}
}
// The result is in dp[1][n-1] (multiplying from matrix 1 to n-1)
return dp[1][n - 1];
}
// Main method to test the solution
public static void main(String[] args) {
Solution sol = new Solution();
int[] nums = {10, 15, 20, 25};
// Output should be 8000
System.out.println(sol.matrixMultiplication(nums));
}
}
class Solution:
def matrixMultiplication(self, nums):
n = len(nums)
dp = [[float('inf')] * n for _ in range(n)]
# A single matrix needs no multiplication cost
for i in range(1, n):
dp[i][i] = 0
# Filling the dp array
for length in range(2, n): # length of the chain
for i in range(1, n - length + 1):
j = i + length - 1
for k in range(i, j):
cost = dp[i][k] + dp[k + 1][j] + nums[i - 1] * nums[k] * nums[j]
if cost < dp[i][j]:
dp[i][j] = cost
# The result is in dp[1][n-1]
return dp[1][n - 1]
# Main method to test the solution
if __name__ == "__main__":
sol = Solution()
nums = [10, 15, 20, 25]
print(sol.matrixMultiplication(nums)) # Output should be 8000
class Solution {
matrixMultiplication(nums) {
const n = nums.length;
const dp = Array.from({ length: n }, () => Array(n).fill(Infinity));
// A single matrix needs no multiplication cost
for (let i = 1; i < n; i++) {
dp[i][i] = 0;
}
// Filling the dp array
for (let length = 2; length < n; length++) { // length of the chain
for (let i = 1; i <= n - length; i++) {
let j = i + length - 1;
for (let k = i; k < j; k++) {
let cost = dp[i][k] + dp[k + 1][j] + nums[i - 1] * nums[k] * nums[j];
if (cost < dp[i][j]) {
dp[i][j] = cost;
}
}
}
}
// The result is in dp[1][n-1]
return dp[1][n - 1];
}
}
// Main method to test the solution
const sol = new Solution();
const nums = [10, 15, 20, 25];
console.log(sol.matrixMultiplication(nums)); // Output should be 8000
Time Complexity: O(N^3) due to three nested loops
Space Complexity: O(N^2) for the dp array storage
Q: Why do we use nums[i-1] × nums[k] × nums[j] in the cost calculation?
A: Multiplying matrices A[i-1] x A[k] with A[k] x A[j] results in a matrix of size A[i-1] x A[j]. The cost of multiplication is the product of these dimensions.
Q: How would you reconstruct the actual parenthesization of the optimal order?
A: Maintain a bracket[][] array to store the split points and backtrack to construct the sequence.
Q: How would you modify this problem if matrix multiplication order was flexible (associative but not commutative)?
A: Use Dynamic Programming with DAG (Directed Acyclic Graph) representation to track possible sequences.