难度 :困难
题目大意:给一个矩阵由0
1
组成的 grid
, 0
表示该位置被占据,给定邮票的高度h
和宽度w
要求 :
问是否能把整个没有被占据的区域占满, 如果可以就返回true
否则false
注意 : 整个矩阵长度和宽度的乘积不超过10^5
grid
做一遍前缀和, 知道两个边界坐标可以判断是否含有被占据点st
数组, 用两个for
循环,将这片区域每个点都标记成1
, 但是这样的时间复杂度很高,不建议使用, 这里就要使用一个类似这种思路的常见方法 二维差分(后续介绍)false
, 反之就是true
一维前缀和的定义 : s[k] = a[1] + a[2] + a[3] + ... + a[k - 1] + a[k]
(下标从1
开始,方便后续处理), 根据定义我们可以写出s[k - 1] = a[1] + a[2] + a[3] + ... + a[k - 1]
, 那么观察可知一个重要的性质s[k] = s[k - 1] + a[k]
(下标从1
开始就是方便这里的k - 1
, 这样数组就不会越界了)
重要性质s[r] - s[l - 1]
就表示l
到r
这一段区间的和,这样我们只需要知道一段区间的左端点和右端点,我们就可以在 O ( 1 ) O(1) O(1)时间求一段区间的和, 证明也很简单,直接带入定义即可
我们可以用s
来表示grid
的前缀和,那么很容易得到下面的s
数组, 方法如下:
for (int i = 1; i <= n; i ++) s[i] = s[i - 1] + grid[i];
(a, b)
左上角所有元素的和,为了处理方便,下标依旧从(1, 1)
开始s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + grid[i][j]
,通过这个公式,我们就可以遍历求出整个前缀和数组, 直观解释如下图l
和r
可以在 O ( 1 ) O(1) O(1)时间求出一段区间的和,那给定两个坐标(x1, y1)
, (x2, y2)
二维怎么在 O ( 1 ) O(1) O(1)求一个区域的和呢, 依旧是根据定义, 直接给出式子s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1]
注意 (x2 >= x1, y2 >= y1)
, 依旧给出直观解释, 如下图:通过这几个方法就可以在 O ( 1 ) O(1) O(1)时间下求出任意区域的和
一维前缀和
s[k] = s[k - 1] + a[k]
s[r] - s[l - 1]
二维前缀和
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + grid[i][j]
s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1]
f[i] = a[i] - a[i - 1]
, f
数组就是a
的差分数组k
, 这个要利用差分和前缀和的联系,差分数组的前缀和就是原数组简单证明: 根据定义 f[i] = a[i] - a[i - 1]
, 写出前一项差分数组 f[i - 1] = a[i - 1] - a[i - 2]
, 那么可以知道f[i] + f[i - 1] = a[i] - a[i - 2]
, 继续递推下去可以得到 a[i] = ∑ 1 i ( f [ k ] ) \sum_1^{i}(f[k]) ∑1i(f[k])我们下标依旧是从1
开始, a[0] = 0
, 这不就是前缀和吗,这样就证明好了
k
呢?l
, 右端点是r
, 则我们可以在l
这个点加上一个k
,求一遍前缀和的话,那么后面所有的点都会加上k
, 并不是我们要的结果,这个时候思考怎么让右端点r
之后的点全部不变呢? 我们可以在r + 1
的位置加上一个-k
,之后求前缀和的话,在r
之后的所有点都会+k + (-k)
就相当于没变了,这样就完成了只在l ~ r
区间上的整体加k
操作简单代码示例
int a[6] = {0, 1, 1, 1, 1, 1}; // a[0]默认是0
int f[10]; // 差分数组
memset(f, 0, sizeof f);
//在一段区间内加上k
function<void(int, int, int)> change = [&](int a, int b, int k) {
f[a] += k, f[b + 1] -= k;
};
// 首先原地修改(相当于在一个点加上一个数)
for (int i = 1; i <= 5; i ++) change(i, i, a[i]);
int l = 2, r = 4;
change(l, r, 1);
for (int i = 1; i <= 5; i ++) f[i] += f[i - 1]; // 前缀和
for (int i = 1; i <= 5; i ++) cout << f[i] << ' ';
输出结果 : 1 2 2 2 1
要注意一点就是首先要先初始化差分数组,也就是上面代码的原地修改
k
, 思路和一维差分基本上类似, 给出公式: 如果要在(x, y)
到(a, b)
加上一个数k
,那么可以有这个公式: s[x][y] += 1, s[x][b + 1] -= 1, s[a + 1][y] -= 1, s[a + 1][b + 1] += 1
下面给出图解:(a, b)
上加上一个k
那么做一遍前缀和之后第一张图的红色区域会全部加上一个k
, 第二、三个图类似,在对应的黄色区域会减去一个k
, 此时在(a + 1, b + 1)
也就是绿色的位置 这个位置之后的所有位置会被减去两次, 所以我们要在那个位置加上一个k
,那么后面的位置就会保持不变了这个题目算法实现步骤:
grid
数组来一遍二维前缀和1
,否则就是0
具体实现代码如下:
class Solution {
public:
using VVI = vector<vector<int>>;
bool possibleToStamp(vector<vector<int>>& grid, int stampHeight, int stampWidth) {
int n = grid.size(), m = grid[0].size();
VVI s(n + 2, vector<int>(m + 2));
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + grid[i - 1][j - 1];
VVI st(n + 2, vector<int>(m + 2));
function<void(int, int, int, int)> insert = [&](int a, int b, int x, int y) -> void {
st[a][b] += 1;
st[a][y + 1] -= 1;
st[x + 1][b] -= 1;
st[x + 1][y + 1] += 1;
};
function<int(int, int, int, int, VVI&)> get = [&](int a, int b, int x, int y, VVI& g) {
return g[x][y] - g[a - 1][y] - g[x][b - 1] + g[a - 1][b - 1];
};
for (int i = stampHeight; i <= n; i ++)
for (int j = stampWidth; j <= m; j ++) {
int x = i - stampHeight + 1, y = j - stampWidth + 1;
int t = get(x, y, i, j, s);
if (!t) insert(x, y, i, j);
}
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++) {
st[i][j] += st[i - 1][j] + st[i][j - 1] - st[i - 1][j - 1];
if (!st[i][j] && !grid[i - 1][j - 1])
return false;
}
return true;
}
};
时间复杂度: O ( n m ) O(nm) O(nm)