Hard
题目描述
给你一个 m x n 的二进制矩阵 grid,每个格子要么是 0(空)要么是 1(被占据)。
现在你有邮票,邮票的尺寸为 stampHeight x stampWidth。我们想要贴邮票,使其满足以下限制和要求:
- 覆盖所有的空格子。
- 不覆盖任何被占据的格子。
- 我们可以放任意数量的邮票。
- 邮票可以相互重叠。
- 邮票不允许旋转。
- 邮票必须完全在网格内。
如果可以在满足上述限制和要求的前提下放置邮票,返回 true;否则返回 false。
示例 1:
输入:grid = [[1,0,0,0],[1,0,0,0],[1,0,0,0],[1,0,0,0],[1,0,0,0]], stampHeight = 4, stampWidth = 3
输出:true
解释:我们有两个重叠的邮票(在图中标记为 1 和 2)能够覆盖所有的空格子。
示例 2:
输入:grid = [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]], stampHeight = 2, stampWidth = 2
输出:false
解释:没有办法在不让邮票超出网格的情况下将邮票放在所有空格子上。
提示:
m == grid.lengthn == grid[r].length1 <= m, n <= 10^51 <= m * n <= 2 * 10^5grid[r][c]不是0就是11 <= stampHeight, stampWidth <= 10^5
解题思路
这道题的核心思路是:贪心地放置邮票,然后检查是否所有空格都被覆盖。
算法步骤:
构建前缀和矩阵:用于快速计算任意子矩形区域内被占据格子的数量,判断是否可以放置邮票。
贪心放置邮票:遍历所有可能的邮票左上角位置,如果该位置可以放置邮票(即邮票覆盖范围内没有被占据的格子),就在该位置放置邮票。
构建覆盖矩阵:记录每个格子被邮票覆盖的情况。使用二维差分数组来高效标记邮票覆盖的区域。
检查覆盖完整性:最后检查所有空格子是否都至少被一个邮票覆盖。
关键技巧:
- 使用前缀和快速判断区域内是否有障碍物(时间复杂度O(1))
- 使用二维差分数组高效标记邮票覆盖区域
- 贪心策略:尽可能多地放置邮票,不会影响最优解
时间复杂度分析:
- 构建前缀和:O(mn)
- 贪心放置邮票:O(mn)
- 构建和检查覆盖:O(mn)
- 总时间复杂度:O(mn)
这种方法能够高效处理大规模数据,满足题目约束条件。
代码实现
class Solution {
public:
bool possibleToStamp(vector<vector<int>>& grid, int stampHeight, int stampWidth) {
int m = grid.size(), n = grid[0].size();
// 构建前缀和数组
vector<vector<int>> prefix(m + 1, vector<int>(n + 1, 0));
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
prefix[i + 1][j + 1] = prefix[i][j + 1] + prefix[i + 1][j] - prefix[i][j] + grid[i][j];
}
}
// 差分数组,用于标记邮票覆盖
vector<vector<int>> diff(m + 2, vector<int>(n + 2, 0));
// 贪心放置邮票
for (int i = 0; i <= m - stampHeight; i++) {
for (int j = 0; j <= n - stampWidth; j++) {
int i2 = i + stampHeight - 1, j2 = j + stampWidth - 1;
// 检查这个区域是否可以放邮票(没有被占据的格子)
int sum = prefix[i2 + 1][j2 + 1] - prefix[i][j2 + 1] - prefix[i2 + 1][j] + prefix[i][j];
if (sum == 0) {
// 在差分数组中标记这个区域
diff[i + 1][j + 1]++;
diff[i + 1][j2 + 2]--;
diff[i2 + 2][j + 1]--;
diff[i2 + 2][j2 + 2]++;
}
}
}
// 从差分数组恢复实际覆盖情况并检查
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
diff[i + 1][j + 1] += diff[i][j + 1] + diff[i + 1][j] - diff[i][j];
if (grid[i][j] == 0 && diff[i + 1][j + 1] == 0) {
return false;
}
}
}
return true;
}
};
class Solution:
def possibleToStamp(self, grid: List[List[int]], stampHeight: int, stampWidth: int) -> bool:
m, n = len(grid), len(grid[0])
# 构建前缀和数组
prefix = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(m):
for j in range(n):
prefix[i + 1][j + 1] = prefix[i][j + 1] + prefix[i + 1][j] - prefix[i][j] + grid[i][j]
# 差分数组,用于标记邮票覆盖
diff = [[0] * (n + 2) for _ in range(m + 2)]
# 贪心放置邮票
for i in range(m - stampHeight + 1):
for j in range(n - stampWidth + 1):
i2, j2 = i + stampHeight - 1, j + stampWidth - 1
# 检查这个区域是否可以放邮票(没有被占据的格子)
area_sum = prefix[i2 + 1][j2 + 1] - prefix[i][j2 + 1] - prefix[i2 + 1][j] + prefix[i][j]
if area_sum == 0:
# 在差分数组中标记这个区域
diff[i + 1][j + 1] += 1
diff[i + 1][j2 + 2] -= 1
diff[i2 + 2][j + 1] -= 1
diff[i2 + 2][j2 + 2] += 1
# 从差分数组恢复实际覆盖情况并检查
for i in range(m):
for j in range(n):
diff[i + 1][j + 1] += diff[i][j + 1] + diff[i + 1][j] - diff[i][j]
if grid[i][j] == 0 and diff[i + 1][j + 1] == 0:
return False
return True
public class Solution {
public bool PossibleToStamp(int[][] grid, int stampHeight, int stampWidth) {
int m = grid.Length, n = grid[0].Length;
// 构建前缀和数组
int[,] prefix = new int[m + 1, n + 1];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
prefix[i + 1, j + 1] = prefix[i, j + 1] + prefix[i + 1, j] - prefix[i, j] + grid[i][j];
}
}
// 差分数组,用于标记邮票覆盖
int[,] diff = new int[m + 2, n + 2];
// 贪心放置邮票
for (int i = 0; i <= m - stampHeight; i++) {
for (int j = 0; j <= n - stampWidth; j++) {
int i2 = i + stampHeight - 1, j2 = j + stampWidth - 1;
// 检查这个区域是否可以放邮票(没有被占据的格子)
int sum = prefix[i2 + 1, j2 + 1] - prefix[i, j2 + 1] - prefix[i2 + 1, j] + prefix[i, j];
if (sum == 0) {
// 在差分数组中标记这个区域
diff[i + 1, j + 1]++;
diff[i + 1, j2 + 2]--;
diff[i2 + 2, j + 1]--;
diff[i2 + 2, j2 + 2]++;
}
}
}
// 从差分数组恢复实际覆盖情况并检查
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
diff[i + 1, j + 1] += diff[i, j + 1] + diff[i + 1, j] - diff[i, j];
if (grid[i][j] == 0 && diff[i + 1, j + 1] == 0) {
return false;
}
}
}
return true;
}
}
var possibleToStamp = function(grid, stampHeight, stampWidth) {
const m = grid.length;
const n = grid[0].length;
// Create prefix sum array to quickly check if area has occupied cells
const prefix = Array(m + 1).fill(0).map(() => Array(n + 1).fill(0));
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
prefix[i][j] = grid[i-1][j-1] + prefix[i-1][j] + prefix[i][j-1] - prefix[i-1][j-1];
}
}
// Function to get sum in rectangle from (r1,c1) to (r2,c2) inclusive
const getSum = (r1, c1, r2, c2) => {
return prefix[r2+1][c2+1] - prefix[r1][c2+1] - prefix[r2+1][c1] + prefix[r1][c1];
};
// Check which positions can place stamps
const canPlace = Array(m).fill(0).map(() => Array(n).fill(false));
for (let i = 0; i <= m - stampHeight; i++) {
for (let j = 0; j <= n - stampWidth; j++) {
if (getSum(i, j, i + stampHeight - 1, j + stampWidth - 1) === 0) {
canPlace[i][j] = true;
}
}
}
// Create prefix sum for canPlace array
const stampPrefix = Array(m + 1).fill(0).map(() => Array(n + 1).fill(0));
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
stampPrefix[i][j] = (canPlace[i-1][j-1] ? 1 : 0) + stampPrefix[i-1][j] + stampPrefix[i][j-1] - stampPrefix[i-1][j-1];
}
}
// Function to get stamp count that can cover position (r,c)
const getStampCount = (r, c) => {
const r1 = Math.max(0, r - stampHeight + 1);
const c1 = Math.max(0, c - stampWidth + 1);
const r2 = Math.min(m - stampHeight, r);
const c2 = Math.min(n - stampWidth, c);
if (r1 > r2 || c1 > c2) return 0;
return stampPrefix[r2+1][c2+1] - stampPrefix[r1][c2+1] - stampPrefix[r2+1][c1] + stampPrefix[r1][c1];
};
// Check if all empty cells can be covered
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (grid[i][j] === 0 && getStampCount(i, j) === 0) {
return false;
}
}
}
return true;
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(mn) | 需要遍历网格构建前缀和、放置邮票和检查覆盖 |
| 空间复杂度 | O(mn) | 需要额外的前缀和数组和差分数组 |
相关题目
. Maximal Square (Medium)
. Bomb Enemy (Medium)
. Matrix Block Sum (Medium)