LeetCode 2132:二维前缀和+差分

文章内容

https://leetcode.cn/problems/stamping-the-grid/description/

一、题目解析

1.1 题意

给一个 m×n 的网格 grid,0 是空地,1 是障碍物。你有一枚 stampHeight × stampWidth 的邮票,只能贴在全为 0 的矩形区域上。邮票可以重叠,同一个格子可以被多张邮票覆盖。问:能不能用若干枚邮票盖满所有 0 格子?

1.2 两个核心问题

这道题要同时解决两件事:

查询——能不能贴。 以 (i,j) 为左上角放一枚邮票,邮票覆盖的矩形区域里有没有障碍物?暴力需要遍历区域内每个格子,O(stampHeight × stampWidth)。

修改——标记覆盖。 确定能贴之后,把这片区域标记为"已覆盖"。暴力同样要逐个格子写,O(stampHeight × stampWidth)。

m×n 最大 2×10^5,邮票面积可以接近整个网格,暴力是 O(mnHW),直接炸。两个操作都必须压到 O(1)——前缀和管查询,差分管修改。

邮票贴放:查询与覆盖

二、二维前缀和:能不能贴

前缀和的作用是 O(1) 回答"任意矩形区域里元素和是多少"。构建公式:

plain text
prefixSum[i][j] = prefixSum[i-1][j] + prefixSum[i][j-1] - prefixSum[i-1][j-1] + grid[i-1][j-1]

prefixSum[i][j] 表示从 (0,0) 到 (i-1,j-1) 这个矩形区域的元素总和。数组开 (row+1)×(col+1),第 0 行和第 0 列天然为 0,边界不用特判。

对于 0-indexed 区域 [r1,c1] 到 [r2,c2] 的查询:

plain text
sum = prefixSum[r2+1][c2+1] - prefixSum[r2+1][c1] - prefixSum[r1][c2+1] + prefixSum[r1][c1]

四项加减就是二维容斥:拿整个大矩形减去左边一条、减去上边一条,再把左上角被减了两次的那块加回来。

如果 sum == 0,说明区域内没有 1,全是空地,可以贴邮票。这就是代码 Part 1 做的事。

三、二维差分:标记覆盖

确定某个位置能贴,就用二维差分 O(1) 记录"这片区域 +1 层覆盖"。邮票可以重叠,策略是贪心的——每个能贴的位置都贴上,反正多贴不会把已覆盖的格子变没。

邮票左上角 (r1,c1),右下角 (r2,c2),四角标记如下:

plain text
diff[r1+1][c1+1] += 1      // 覆盖起点,影响向右下扩散diff[r1+1][c2+2] -= 1      // 右边界外一列,截断横向扩散diff[r2+2][c1+1] -= 1      // 下边界外一行,截断纵向扩散diff[r2+2][c2+2] += 1      // 右下被截断两次,补回

下标偏移 +1 和 +2 是因为 diff 开到了 row+2,还原公式走的是 1-indexed 前缀和。r2+2 最大等于 (row-1)+2 = row+1。

还原用二维前缀和:对 diff 再做一次前缀和,标记展开为实际覆盖层数:

plain text
diff[i][j] += diff[i-1][j] + diff[i][j-1] - diff[i-1][j-1]

二维差分:标记与还原

四、代码详解

java
class Solution {    public boolean possibleToStamp(int[][] grid, int stampHeight, int stampWidth) {        int row = grid.length;        int col = grid[0].length;        // prefixSum 开 row+1,1-indexed,第 0 行/列是哨兵        int[][] prefixSum = new int[row + 1][col + 1];        // diff 开 row+2,四角标记最大下标到 row+1        int[][] diff = new int[row + 2][col + 2];         // ===== Part 1:构建前缀和 =====        for (int i = 1; i <= row; i++) {            for (int j = 1; j <= col; j++) {                prefixSum[i][j] = prefixSum[i][j - 1] + prefixSum[i - 1][j]                        + grid[i - 1][j - 1] - prefixSum[i - 1][j - 1];            }        }         // ===== Part 2:贪心贴邮票 =====        for (int i = 0; i < row; i++) {            for (int j = 0; j < col; j++) {                int row1 = i;                int col1 = j;                int row2 = i + stampHeight - 1;                int col2 = j + stampWidth - 1;                // 邮票右下角超出网格,这个左上角位置没法贴                if (row2 >= row || col2 >= col) {                    continue;                }                // 前缀和 O(1) 判断区域内是否有障碍物                int locationSum = prefixSum[row2 + 1][col2 + 1]                        - prefixSum[row2 + 1][col1]                        - prefixSum[row1][col2 + 1]                        + prefixSum[row1][col1];                // 区域内全为 0,可以贴                if (locationSum == 0) {                    // 二维差分四角标记,O(1) 完成区域覆盖                    diff[row1 + 1][col1 + 1] += 1;                    diff[row1 + 1][col2 + 2] -= 1;                    diff[row2 + 2][col1 + 1] -= 1;                    diff[row2 + 2][col2 + 2] += 1;                }            }        }         // ===== Part 3:差分还原 + 最终检查 =====        for (int i = 1; i <= row; i++) {            for (int j = 1; j <= col; j++) {                // 二维前缀和还原:标记 + 上 + 左 - 左上                diff[i][j] += diff[i - 1][j] + diff[i][j - 1] - diff[i - 1][j - 1];                // 空地且未被任何邮票覆盖 → 失败                if (diff[i][j] == 0 && grid[i - 1][j - 1] == 0) {                    return false;                }            }        }        return true;    }}

为什么可以贪心。 邮票可以重叠——某张邮票贴上去,只会让覆盖层数 +1,不会把原本已覆盖的格子变成未覆盖。所以不需要做"贴或不贴"的决策,每个能贴的位置都直接贴上就行。

**diff 开 row+2 : r2 最大 row-1,标记写到 r2+2 ≤ row+1。diff 开到 row+2 正好兜住。col 方向同理。

两个前缀和公式的对应关系。 Part 1 的 prefixSum[i][j] = ... + grid[i-1][j-1] 是从原数组构建前缀和;Part 3 的 diff[i][j] += ... 是从差分标记还原为实际值。都是同一个容斥:当前 = 左 + 上 − 左上 + 自身。

最终检查的逻辑。 diff[i][j] 是格子 (i-1,j-1) 被覆盖的总层数。如果这个格子本是空地(grid0)但层数为 0,说明没有任何邮票覆盖到它,返回 false。

五、复杂度分析

时间 O(row × col):三次独立遍历网格:Part 1 构建前缀和、Part 2 贪心贴邮票、Part 3 差分还原,每次都是遍历全部格子,代入最大约为 2×10^5。

空间 O(row × col):prefixSum 和 diff 各占一份。


本文由 AgentPlanFlow 生成

觉得不错?给作者点个赞吧

分类:题解

评论 (0)

无需注册,留言经站长审核后公开。

加载评论中…

LeetCode 2132:二维前缀和+差分 · Yinchen 的博客