Given an array arr[] which represents the dimensions of a sequence of matrices where the ith matrix has the dimensions (arr[i-1] x arr[i]) for i>=1, find the most efficient way to multiply these matrices together. The efficient way is the one that involves the least number of multiplications.
Examples:
Input: arr[] = [2, 1, 3, 4] Output: 20 Explanation: There are 3 matrices of dimensions 2 × 1, 1 × 3, and 3 × 4, Let this 3 input matrices be M1, M2, and M3. There are two ways to multiply: ((M1 x M2) x M3) and (M1 x (M2 x M3)), note that the result of (M1 x M2) is a 2 x 3 matrix and result of (M2 x M3) is a 1 x 4 matrix.
((M1 x M2) x M3) requires (2 x 1 x 3) + (2 x 3 x 4) = 30 (M1 x (M2 x M3)) requires (1 x 3 x 4) + (2 x 1 x 4) = 20.
The minimum of these two is 20.
Input: arr[] = [1, 2, 3, 4, 3] Output: 30 Explanation: There are 4 matrices of dimensions 1 × 2, 2 × 3, 3 × 4, 4 × 3. Let this 4 input matrices be M1, M2, M3 and M4. The minimum number of multiplications are obtained by ((M1 x M2) x M3) x M4). The minimum number is (1 x 2 x 3) + (1 x 3 x 4) + (1 x 4 x 3) = 30.
Input: arr[] = [3, 4] Output: 0
Explanation: As there is only one matrix so, there is no cost of multiplication.
Constraints:
2 ≤ arr.size() ≤ 100
1 ≤ arr[i] ≤ 200
Approach 01:
-
C++
-
Python
#include <vector> #include <climits> using namespace std; class Solution { public: int matrixChainMultiplication(vector<int>& dimensions, int left, int right, vector<vector<int>>& memo) { if (left == right) { return 0; } if (memo[left][right] != -1) { return memo[left][right]; } int minOperations = INT_MAX; for (int partition = left; partition <= right - 1; partition++) { int currentOperations = matrixChainMultiplication(dimensions, left, partition, memo) + matrixChainMultiplication(dimensions, partition + 1, right, memo) + dimensions[left - 1] * dimensions[partition] * dimensions[right]; minOperations = min(minOperations, currentOperations); } return memo[left][right] = minOperations; } int matrixMultiplication(vector<int>& dimensions) { int numMatrices = dimensions.size(); if (numMatrices <= 2) return 0; // Edge case: no multiplication needed vector<vector<int>> memo(numMatrices, vector<int>(numMatrices, -1)); return matrixChainMultiplication(dimensions, 1, numMatrices - 1, memo); } };
from typing import List class Solution: def matrixChainMultiplication(self, dimensions: List[int], left: int, right: int, memo: List[List[int]]) -> int: if left == right: return 0 if memo[left][right] != -1: return memo[left][right] minOperations = float('inf') for partition in range(left, right): currentOperations = (self.matrixChainMultiplication(dimensions, left, partition, memo) + self.matrixChainMultiplication(dimensions, partition + 1, right, memo) + dimensions[left - 1] * dimensions[partition] * dimensions[right]) minOperations = min(minOperations, currentOperations) memo[left][right] = minOperations return minOperations def matrixMultiplication(self, dimensions: List[int]) -> int: numMatrices = len(dimensions) if numMatrices <= 2: return 0 # Edge case: no multiplication needed memo = [[-1] * numMatrices for _ in range(numMatrices)] return self.matrixChainMultiplication(dimensions, 1, numMatrices - 1, memo)
Time Complexity:
- Recursive Calls:
The function recursively explores all possible partitions of the matrix chain. Since there are \( O(n^2) \) unique subproblems and each takes \( O(n) \) time to compute, the total complexity is \( O(n^3) \).
- Total Time Complexity:
The overall time complexity is \( O(n^3) \).
Space Complexity:
- Memoization Table:
The algorithm uses a \( n \times n \) memoization table, requiring \( O(n^2) \) space.
- Recursive Call Stack:
In the worst case, the recursive depth reaches \( O(n) \), leading to an additional \( O(n) \) space usage.
- Total Space Complexity:
The overall space complexity is \( O(n^2) \).