问题描述
小R手里有一个大小为 n 行 m 列的矩形蛋糕,每个小正方形区域都有一个代表美味度的整数。小R打算切割出一个正方形的小蛋糕给自己,而剩下的部分将给小S。她希望两人吃的部分的美味度之和尽量接近。
我们定义小R吃到的部分的美味度之和为 s_1,而小S吃到的部分的美味度之和为 s_2,请你帮助小R找到一个切割方案,使得 |s_1 - s_2| 的值最小。
测试样例
样例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
题目分析
本题目要求从一个矩形蛋糕中找到一个正方形子区域,使得该区域的美味度之和和剩余部分的美味度之和之间的差值最小。我们需要返回这个最小差值。
为了实现这个目标,我们可以使用暴力枚举结合前缀和的方式来高效地解决问题。
解题思路
1. 使用二维前缀和优化区域求和
-
二维前缀和的定义: 前缀和是一种高效计算矩形区域和的工具。我们构建一个二维数组
prefix_sum,其中prefix_sum[i][j]表示从矩阵左上角 (1,1)(1, 1) 到当前点 (i,j)(i, j) 的所有元素的总和。前缀和的递推公式为:
prefix_sum[i][j]=a[i−1][j−1]+prefix_sum[i−1][j]+prefix_sum[i][j−1]−prefix_sum[i−1][j−1]\text{prefix_sum}[i][j] = a[i-1][j-1] + \text{prefix_sum}[i-1][j] + \text{prefix_sum}[i][j-1] - \text{prefix_sum}[i-1][j-1]
- 通过前缀和,我们可以在 O(1)O(1) 时间内计算任意矩形区域的元素和。
-
区域求和公式: 设正方形区域的左上角为 (x1,y1)(x_1, y_1),右下角为 (x2,y2)(x_2, y_2),其和可以通过前缀和数组快速计算:
s1=prefix_sum[x2][y2]−prefix_sum[x1−1][y2]−prefix_sum[x2][y1−1]+prefix_sum[x1−1][y1−1]s_1 = \text{prefix_sum}[x_2][y_2] - \text{prefix_sum}[x_1-1][y_2] - \text{prefix_sum}[x_2][y_1-1] + \text{prefix_sum}[x_1-1][y_1-1]
2. 遍历所有可能的正方形子区域
- 枚举每个正方形区域的左上角位置 (x1,y1)(x_1, y_1)。
- 确定正方形边长 kk(需保证正方形在矩阵范围内)。
- 使用前缀和计算正方形区域的美味度之和 s1s_1,并求出剩余部分的美味度之和 s2=total_sum−s1s_2 = \text{total_sum} - s_1。
- 比较 ∣s1−s2∣|s_1 - s_2| 的值,更新全局最小值。
3. 返回最小差值
最终返回所有可能方案中的最小差值。
代码实现
以下是完整的代码:
def solution(n: int, m: int, a: list) -> int:
# 构建前缀和数组
prefix_sum = [[0] * (m + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for j in range(1, m + 1):
prefix_sum[i][j] = (a[i - 1][j - 1]
+ prefix_sum[i - 1][j]
+ prefix_sum[i][j - 1]
- prefix_sum[i - 1][j - 1])
# 蛋糕总美味度
total_sum = prefix_sum[n][m]
min_diff = float('inf')
# 遍历所有可能的正方形子区域
for x1 in range(1, n + 1):
for y1 in range(1, m + 1):
for k in range(min(n - x1 + 1, m - y1 + 1)):
x2, y2 = x1 + k, y1 + k
s1 = (prefix_sum[x2][y2]
- prefix_sum[x1 - 1][y2]
- prefix_sum[x2][y1 - 1]
+ prefix_sum[x1 - 1][y1 - 1])
s2 = total_sum - s1
min_diff = min(min_diff, abs(s1 - s2))
return min_diff
复杂度分析
时间复杂度
-
前缀和构建: 构建前缀和数组的时间复杂度为 O(n×m)O(n \times m)。
-
正方形区域枚举: 枚举每个左上角点 (x1,y1)(x_1, y_1) 的时间复杂度为 O(n×m)O(n \times m),对每个起点调整正方形边长的时间复杂度为 O(min(n,m))O(\min(n, m))。
因此总时间复杂度为:
O(n×m×min(n,m))O(n \times m \times \min(n, m))
空间复杂度
- 前缀和数组占用 O(n×m)O(n \times m) 的空间。
测试样例
if __name__ == '__main__':
# 测试样例1
assert solution(3, 3, [[1, 2, 3], [2, 3, 4], [3, 2, 1]]) == 1
# 测试样例2
assert solution(4, 4, [[1, 2, 3, 4], [4, 3, 2, 1], [1, 2, 3, 4], [4, 3, 2, 1]]) == 2
# 测试样例3
assert solution(2, 2, [[5, 5], [5, 5]]) == 10
思考与优化
- 优化边长枚举:可以根据 ∣s1−s2∣|s_1 - s_2| 的变化规律,提前剪枝跳过某些不必要的边长,减少无效计算。
- 并行计算:利用多线程或GPU加速矩阵前缀和的计算,适用于超大规模数据。
总结
该解法利用了二维前缀和来优化区域求和,使得暴力枚举能够在合理的时间内完成所有可能方案的计算,最终实现了 ∣s1−s2∣|s_1 - s_2| 的最小化。