核心逻辑:从一条边开始“切分”#
假设有一个凸 边形,顶点数值存在数组 中。我们的目标是将它剖分成 个三角形,使得所有三角形顶点的乘积之和最小。
可以想象手中拿着一个凸多边形,每次切去一个角(一个三角形),直到最后只剩一个三角形。因此与其纠结「第一次切去哪个三角形」,不如考虑「最后保留哪个三角形」。
对于任何一个由顶点 到顶点 构成的多边形(记为区间 ),我们可以固定底边(连接顶点 和 的边)。在最终的剖分方案中,这条底边一定属于某一个三角形。
假设这个三角形的第三个顶点是 (其中 在 和 之间),那么这个三角形 就把原来的多边形切成了三部分:
- 左边: 由顶点 到 构成的多边形。
- 中间: 三角形 本身。
- 右边: 由顶点 到 构成的多边形。
定义状态与转移方程#
- 状态定义: 表示从顶点 到顶点 连成的子多边形进行三角剖分后的最低得分。
- 转移方程: 我们需要枚举 和 之间所有的可能顶点 :
- 边界条件:
- 如果 和 之间没有顶点(即 ),无法形成三角形,。
- 当 时, 就是唯一的那个三角形 。
遍历顺序#
观察方程, 依赖于 和 :
- :在 的左侧(同一行)。
- :在 的下方(不同行,)。
因此,遍历顺序与516.最长回文子序列一致: 从大到小(从下往上), 从小到大(从左往右)。
代码实现#
public int minScoreTriangulation(int[] values) {
int n = values.length;
int[][] dp = new int[n][n];
// i 从下往上遍历
for (int i = n - 3; i >= 0; i--) {
// j 从左往右遍历,且 j 与 i 之间至少要隔一个点
for (int j = i + 2; j < n; j++) {
// 初始化为一个较大值
int minRes = Integer.MAX_VALUE;
// 枚举中间顶点 k
for (int k = i + 1; k < j; k++) {
int score = dp[i][k] + dp[k][j] + values[i] * values[k] * values[j];
minRes = Math.min(minRes, score);
}
dp[i][j] = minRes;
}
}
return dp[0][n - 1];
}java