Leetcode第 69 场双周赛_用邮票贴满网格图

问题描述

给定一个n*m大小的棋盘,每个棋盘格都有一个值1或0,现需要你利用给定的长度为stampWidth*stampHeight的长方形去覆盖棋盘格为0的区域。
问题链接:用邮票贴满网格图

覆盖准则为:

  • 长方形不能旋转
  • 长方形尺寸固定
  • 允许重叠覆盖
  • 长方形不能覆盖到棋盘格为1的区域
  • 长方形数量不限
  • 长方形必须完全在矩阵内

基本思路

由题可知,长方形的覆盖可以重叠,因此对于每一个值为0的棋盘格,都需要被覆盖。如果使用二维数组pos[i][j],pos[i][j]=0代表棋盘格(i,j)没有覆盖并且本来的值也为0,pos[i][j]=1代表棋盘格(i,j)被覆盖或本来的值为1
在这里插入图片描述

显然我们可以对于初始棋盘格每个为0的格子模拟覆盖,以该格子为长方形左上角进行覆盖(否则别人覆盖的时候也不会覆盖到它了),检测该区域都为空,并对该区域全体每个棋盘格pos[][]赋值为1。显然得到对于模拟长方形覆盖是二维的,如果单纯的模拟循环必定会超时,因此如何将覆盖(即对区间进行整体+1)转换为一维是关键问题。

Q:如何得知该区域是否全部为0?

A: 使用前缀和得到存储初始棋盘的分布,实现O(1)得到该区域内的情况,只有区域总和为0的时候才能覆盖;

Q:如何将覆盖的区间整体进行+1操作?

A:使用二维差分数组,实现O(1)对二维区间进行区间操作。

Q:如何判断能否成功覆盖?

A:将二维差分数组转换为正常数组,因为在覆盖的时候满足不会覆盖原始棋盘中为1的棋盘格,模拟后需要检查原始棋盘格为0的格子(空的格子)是否在模拟后的数组中仍为0,如果存在,则代表没有完全覆盖。

知识点分析

前缀和:给定一个长度为n的数组,求出任意第i个数之前的数的和。

一维前缀和

dp[i]表示第i个数之前的数的和,即前缀和。
在这里插入图片描述

二位前缀和

递推公式:sum[x][y]=sum[x-1][y]+sum[x][y-1]-sum[x-1][y-1]+a[x][y]
在这里插入图片描述

一维差分
  • 差分数组b:b[i]=a[i]-a[i-1] 为每一个数和前一个数的
  • 原始数组和差分关系:原始数组a 为差分数组的前缀和。
  • 基本算法:
    数组区间[i,j]的所有值+c 等价于 对差分数组b[i]=b[i]+c; b[j+1]=b[j+1]-c;
    因为对于(i,j)这个区间中,每一个值在对差分数组求前缀和(即得到原始数组)的过程中都+c,而对于(j+1,…)之后的区间则+c-c抵消
二维差分

二维差分是一维差分的扩展,但是始终坚守原始数组为差分数组前缀和的原理。

  1. 根据二维前缀和,可以得出如果直接使差分数组b[x1][y1]+=c的操作会使下图蓝色区间所有值都+c
    在这里插入图片描述
  2. 因此,我们需要删去以下两块红色区域的+c,即使b[x1][x2+1]-c, b[x2+1][y1]-c, 由于多减了一次重叠部分,因此再加上重叠的部分——黄色部分(即下图中黄色部分+c)
    在这里插入图片描述
    在这里插入图片描述
    因此当对(x1,y1), (x2,y2)区域内做+c操作,得到运算式为:
b[x1][y1]+=c;
b[x1][y2+1]-=c;
b[x2+1][y1]-=c;
b[x2+1][y2+1]+=c;

代码呈现

回到该题的最初思路,可以得到代码:

class Solution {
public:
    bool possibleToStamp(vector<vector<int>>& grid, int stampHeight, int stampWidth) {
        int n=grid.size();
        int m=grid[0].size();
        
        int prefix[n+2][m+2];
        for(int i=0;i<=n;i++){
            for(int j=0;j<=m;j++)prefix[i][j]=0;
        }
        //求出棋盘的前缀和
        for(int i=0;i<grid.size();i++){
            for(int j=0;j<grid[i].size();j++){
                prefix[i+1][j+1]=prefix[i][j+1]+prefix[i+1][j]-prefix[i][j]+grid[i][j];
            }
        }
        int st[n+2][m+2];
         for(int i=0;i<=n;i++){
            for(int j=0;j<=m;j++)st[i][j]=0;
        }
        for(int i=1;i<=n-stampHeight+1;i++){
            for(int j=1;j<=m-stampWidth+1;j++){
                int x1=i+stampHeight-1,y1=j+stampWidth-1;
                //区间的值
                int pos=prefix[x1][y1]-prefix[x1][j-1]-prefix[i-1][y1]+prefix[i-1][j-1];
                //二维差分给这个区间覆盖邮票(即i,j->x1,y1这个长方形都+1)
                if(pos==0){
                    st[i][j]++;
                    st[x1+1][j]--;
                    st[i][y1+1]--;
                    st[x1+1][y1+1]++;
                }
            }
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++)
                //恢复差分即求前缀和
                st[i][j]=st[i][j]+st[i-1][j]+st[i][j-1]-st[i-1][j-1];
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                //如果该点本来就为空并且没有邮票覆盖
                if(grid[i-1][j-1]==0&&st[i][j]==0)
                    return false;
            }
        }
        return 1;
        
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值