题目总结笔记
问题描述
小R有一个大小为 n 行 m 列的矩形蛋糕,每个小正方形区域都有一个代表美味度的整数。小R打算从中切割出一个正方形的小蛋糕给自己,而剩下的部分将给小S。她希望两人吃的部分的美味度之和尽量接近。
定义:
- 小R吃到的部分的美味度之和为
s_1,小S吃到的部分的美味度之和为s_2。 - 目标是通过选择一个正方形区域使得
|s_1 - s_2|最小,即使得两部分的美味度差尽可能小。
输入输出
-
输入:
n:矩阵的行数,表示蛋糕的行数。m:矩阵的列数,表示蛋糕的列数。a:一个n行m列的二维数组,表示蛋糕的美味度。
-
输出:
- 一个整数,表示切割后的两部分美味度差的最小值。
示例
样例1:
输入:
n = 3, m = 3, a = [[1, 2, 3], [2, 3, 4], [3, 2, 1]]
输出:
1
样例2:
输入:
n = 4, m = 4, a = [[1, 2, 3, 4], [4, 3, 2, 1], [1, 2, 3, 4], [4, 3, 2, 1]]
输出:
2
样例3:
输入:
n = 2, m = 2, a = [[5, 5], [5, 5]]
输出:
10
解题思路
-
问题理解:
- 题目要求我们从一个矩阵中选择一个正方形区域,并使得这部分和剩下部分的美味度差最小。目标是最小化两部分的美味度差,即
|s1 - s2|,其中s1是切割出的正方形部分的美味度,s2是剩余部分的美味度。
- 题目要求我们从一个矩阵中选择一个正方形区域,并使得这部分和剩下部分的美味度差最小。目标是最小化两部分的美味度差,即
-
总美味度的计算:
- 首先,我们需要计算整个矩阵的美味度之和
total_sum,即矩阵所有元素的和。
- 首先,我们需要计算整个矩阵的美味度之和
-
正方形区域选择:
- 正方形的大小可以从
1x1到min(n, m)xmin(n, m),我们需要遍历所有可能的正方形区域,计算每个正方形区域的美味度s1,并计算剩余部分的美味度s2 = total_sum - s1。
- 正方形的大小可以从
-
计算美味度差:
- 对于每个正方形区域,我们计算其美味度差
|2*s1 - total_sum|,并更新最小的美味度差。
- 对于每个正方形区域,我们计算其美味度差
-
优化计算:
- 为了高效地计算不同位置的正方形区域的美味度之和,我们可以使用 二维前缀和(二维累加和)来快速计算任意子矩阵的和。
- 前缀和数组
prefixSum[i][j]表示从矩阵的(0, 0)到(i-1, j-1)区域的美味度之和。这样可以在常数时间内计算任意矩形区域的和。
Java代码实现
public class Main {
public static int solution(int n, int m, int[][] a) {
int total_sum = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
total_sum += a[i][j];
}
}
int[][] prefixSum = new int[n + 1][m + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
prefixSum[i][j] = a[i - 1][j - 1]
+ prefixSum[i - 1][j]
+ prefixSum[i][j - 1]
- prefixSum[i - 1][j - 1];
}
}
int minDiff = Integer.MAX_VALUE;
for (int size = 1; size <= Math.min(n, m); size++) {
for (int i = 0; i <= n - size; i++) {
for (int j = 0; j <= m - size; j++) {
int s1 = prefixSum[i + size][j + size]
- prefixSum[i][j + size]
- prefixSum[i + size][j]
+ prefixSum[i][j];
int diff = Math.abs(2 * s1 - total_sum);
minDiff = Math.min(minDiff, diff);
}
}
}
return minDiff;
}
public static void main(String[] args) {
System.out.println(solution(3, 3, new int[][]{{1, 2, 3}, {2, 3, 4}, {3, 2, 1}}) == 1);
System.out.println(solution(4, 4, new int[][]{{1, 2, 3, 4}, {4, 3, 2, 1}, {1, 2, 3, 4}, {4, 3, 2, 1}}) == 2);
System.out.println(solution(2, 2, new int[][]{{5, 5}, {5, 5}}) == 10);
}
}
解题步骤和思路总结
- 计算总美味度:首先计算整个矩阵的美味度总和。
- 二维前缀和:通过二维前缀和数组,快速计算任意子矩阵的美味度之和。
- 遍历所有正方形:从最小的正方形开始,遍历所有可能的正方形区域,计算其美味度之和,并更新最小的差值。
- 返回结果:最终返回最小的美味度差。
时间复杂度分析
-
时间复杂度:
- 计算总美味度:
O(n * m)。 - 计算二维前缀和:
O(n * m)。 - 遍历所有可能的正方形区域:
O(min(n, m) * n * m),最坏情况下可能遍历所有正方形。
所以,总时间复杂度大致为:
O(n * m * min(n, m))。 - 计算总美味度:
-
空间复杂度:
- 使用二维前缀和数组,空间复杂度为
O(n * m)。
- 使用二维前缀和数组,空间复杂度为
结论
通过二维前缀和优化,可以高效地求解矩阵中所有可能的正方形区域的美味度差,并得到最优的切割方案。