小R的蛋糕分享| 豆包MarsCode AI 刷题

103 阅读4分钟

问题描述

小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

思考与优化

  1. 优化边长枚举:可以根据 ∣s1−s2∣|s_1 - s_2| 的变化规律,提前剪枝跳过某些不必要的边长,减少无效计算。
  2. 并行计算:利用多线程或GPU加速矩阵前缀和的计算,适用于超大规模数据。

总结

该解法利用了二维前缀和来优化区域求和,使得暴力枚举能够在合理的时间内完成所有可能方案的计算,最终实现了 ∣s1−s2∣|s_1 - s_2| 的最小化。