一、题目描述
给定n个矩阵{A1,A2,A3,…,An},其中,Ai和Ai+1(i=1,2,…,n-1)是可乘的。用括号的方法表示矩阵连乘的次序,不同的计算次序计算量(乘法次数)是不同,找出一种加括号的方法,使得矩阵连乘的计算量最小。
设两个矩阵Mixj、Mjxp相乘运算次数则为i x j x p。
示例:
A1是M5x10的矩阵;
A2是M5x100的矩阵;
A3是M100x2的矩阵;
那么有两种加括号的方法:
(1) (A1A2)A3;
(2) A1(A2A3);
第一种加括号方法运算量:5 x 10 x100 + 5 x 100 x 2 = 6000;
第二种加括号方法运算量:10x 100 x2 + 5 x 10 x 2 = 2100;
二、解题思路
设输入矩阵如下表格:
矩阵 | A1 | A2 | A3 | A4 | A5 |
---|---|---|---|---|---|
规模 | 3 x 5 | 5 x 10 | 10 x 8 | 8 x 2 | 2 x 4 |
转化为数组P表示为:
P[0] | P[1] | P[2] | P[3] | P[4] | P[5] |
---|---|---|---|---|---|
3 | 5 | 10 | 8 | 2 | 4 |
所以P[0]和P[1]表示A1,P[1]和[2]表示A2以此类推即可。
1. 定义状态
设dp[i][j]表示Ai到Aj所需要最小计算量,i和j从1开始计数,那么我们最终要求出的是dp[1][P.length -1]即为矩阵A1到An的最小计算量,其中n = P.length -1;
我么可以对dp[i][j]进行拆分,如果i和j满足j - i >= 1时,则i和j中间必有一点k,即k表示矩阵Ak。可以从Ak进行拆分两个序列(Ai到Ak)和(Ak+1到Aj),两个序列乘法最少的计算量分别为dp[i][k]和dp[k+1][j]。两个子序列矩阵合并后计算量为P[i-1] x P[K] x P[j]。所以dp[i][j] = dp[i][k] + dp[k+1][j] + P[i-1] x P[K] x P[j],当然位置k可能有多个,我们取最小花费那个。
2. 定义状态转移方程
当j - i <= 0时,有
d p [ i ] [ j ] = 0 dp[i][j] = 0 dp[i][j]=0
否则
d p [ i ] [ j ] = m a x ( d p [ i ] [ k ] + d p [ k ] [ j ] + P [ i − 1 ] × P [ k ] × P [ j ] ) , i ≤ k < j dp[i][j] = max( dp[i][k] + dp[k][j] + P[i-1] \times P[k] \times P[j]), i \leq k < j dp[i][j]=max(dp[i][k]+dp[k][j]+P[i−1]×P[k]×P[j]),i≤k<j
3. 初始化
当j - i <= 0时,有 d p [ i ] [ j ] = 0 dp[i][j] = 0 dp[i][j]=0
4. 计算方式
自底向上,自左向右计算。这里解释下是指dp二维表自底向上是指i从大往小计算,自左向右计算是指j从小到大计算。
三、代码实现
/**
* 最小矩阵乘法运算数量
*
* @author hh
* @date 2021-5-18 23:32
*/
public class MinMatrixChain {
public int minCalcCount(int[] matrix, int[][] trace) {
if (matrix.length < 3) {
throw new IllegalArgumentException("非法参数");
}
int[][] dp = new int[matrix.length][matrix.length];
for (int i = matrix.length - 1; i >= 1; i--) {
for (int j = 1; j <= matrix.length-1; j++) {
if (j - i == 0) {
dp[i][j] = 0;
continue;
}
dp[i][j] = Integer.MAX_VALUE;
for (int k = i; k < j; k++) {
int temp = dp[i][k] + dp[k+1][j] + matrix[i - 1] * matrix[k] * matrix[j];
if (temp < dp[i][j]) {
dp[i][j] = temp;
trace[i][j] = k;
}
}
}
}
return dp[1][matrix.length-1];
}
public void print(int[] matrix, int[][] trace, int startIndex, int endIndex) {
if (endIndex - startIndex <= 0) {
System.out.print("A" + startIndex + " ");
} else {
System.out.print("(");
print(matrix, trace, startIndex, trace[startIndex][endIndex]);
print(matrix, trace, trace[startIndex][endIndex] + 1, endIndex);
System.out.print(")");
}
}
public static void main(String[] args) {
int[] matrix = new int[]{3, 5, 10, 8, 2, 4};
int[][] trace = new int[matrix.length][matrix.length];
MinMatrixChain minMatrixChain = new MinMatrixChain();
System.out.println(minMatrixChain.minCalcCount(matrix, trace));
minMatrixChain.print(matrix, trace, 1, matrix.length-1);
}
}
四、执行结果
五、思考
本题和字符串切分的做法非常相似,都是对dp数组进行线性划分,读者有时间可以看我的另一篇文章动态规划经典题目-字符串切分,进行举一反三。