[LeetCode] Bricks Falling When Hit 碰撞时砖头掉落
We have a grid of 1s and 0s; the 1s in a cell represent bricks. A brick will not drop if and only if it is directly connected to the top of the grid, or at least one of its (4-way) adjacent bricks will not drop.
We will do some erasures sequentially. Each time we want to do the erasure at the location (i, j), the brick (if it exists) on that location will disappear, and then some other bricks may drop because of that erasure.
Return an array representing the number of bricks that will drop after each erasure in sequence.
Example 1:
Input:
grid = [[1,0,0,0],[1,1,1,0]]
hits = [[1,0]]
Output: [2]
Explanation:
If we erase the brick at (1, 0), the brick at (1, 1) and (1, 2) will drop. So we should return 2.
Example 2:
Input:
grid = [[1,0,0,0],[1,1,0,0]]
hits = [[1,1],[1,0]]
Output: [0,0]
Explanation:
When we erase the brick at (1, 0), the brick at (1, 1) has already disappeared due to the last move. So each erasure will cause no bricks dropping. Note that the erased brick (1, 0) will not be counted as a dropped brick.
Note:
- The number of rows and columns in the grid will be in the range [1, 200].
- The number of erasures will not exceed the area of the grid.
- It is guaranteed that each erasure will be different from any other erasure, and located inside the grid.
- An erasure may refer to a location with no brick - if it does, no bricks drop.
这道题给了我们一个由0和1组成的grid,说是1代表砖头,当砖头连着顶端的时候,就不会掉落,当某个砖头连着不会掉落的砖头时,其本身也不会掉落。然后我们要去掉一些砖头,当去掉某个砖头时,与其相连的砖头可能也会同时掉落。所以这里让我们求同时掉落的砖头个数。博主书读的不少,不会受骗,这尼玛不就是泡泡龙游戏么。其中泡泡龙的一大技巧就是挂葡萄,当关键节点处的泡泡被打掉后,这整个一串都可以直接掉下来。这里也是一样啊,grid的顶端就是游戏界面的顶端,然后砖头就是泡泡,去掉砖头就是消掉某个地方的泡泡,然后掉落的砖头就是掉下的泡泡啦。游戏玩的6,不代表题会做,其实这道题还是蛮有难度的,花了博主很长的时间。
首先我们来想,我们肯定要统计出当前没有掉落的砖头数量,当去掉某个砖头后,我们可以统计当前还连着的砖头数量,二者做差值就是掉落的砖头数量。那么如何来统计不会掉落的砖头数量呢,由于顶层的砖头时不会掉落的,那么跟顶层相连的所有砖头肯定也不会掉落,我们就可以使用DFS来遍历,我们可以把不会掉落的砖头位置存入一个HashSet中,这样通过比较不同状态下HashSet中元素的个数,我们就知道掉落了多少砖头。然后我们再来想一个问题,在没有去除任何砖头的时候,我们DFS查找会遍历所有的砖头,当某个砖头去除后,可能没有连带其他的砖头,那么如果我们再来遍历一遍所有相连的砖头,相当于又把整个数组搜索了一遍,这样并不是很高效。我们可以试着换一个思路,如果我们先把要去掉的所有砖头都先去掉,这样我们遍历所有相连的砖头就是最终还剩下的砖头,然后我们从最后一个砖头开始往回加,每加一个砖头,我们就以这个砖头为起点,DFS遍历其周围相连的砖头,加入HashSet中,那么只会遍历那些会掉的砖头,那么增加的这些砖头就是会掉的砖头数量了,然后再不停的在增加前面的砖头,直到把hits中所有的砖头都添加回来了,那么我们也就计算出了每次会掉的砖头的个数。
好,我们使用一个HashSet来保存不会掉落的砖头,然后先遍历hits数组,把要掉落的砖头位置的值都减去一个1,这里有个需要注意的地方,hits里的掉落位置实际上在grid中不一定有砖头,就是说可能是本身为0的位置,那么我们减1后,数组中也可能会有-1,没有太大的影响,不过需要注意一下,这里不能使用 if (grid[i][j]) 来直接判断其是否为1,因为非0值-1也会返回true。然后我们对第一行的砖头都调用递归函数,因为顶端的砖头不会掉落,跟顶端的砖头相连的砖头也不会掉落,所以要遍历所有相连的砖头,将位置都存入noDrop。然后就是从最后一个位置往前加砖头,先记录noDrop当前的元素个数,然后grid中对应的值自增1,之后增加后的值为1了,才说明这块之前是有砖头的,然后我们看其上下左右位置,若有砖头,则对当前位置调用递归,还有一种情况是当前是顶层的话,还是要调用递归。递归调用完成后二者的差值再减去1就是掉落的砖头数,减1的原因是去掉的砖头不算掉落的砖头数中,参见代码如下:
解法一:
class Solution {
public:
vector<int> hitBricks(vector<vector<int>>& grid, vector<vector<int>>& hits) {
int m = grid.size(), n = grid[].size(), k = hits.size();
vector<int> res(k);
unordered_set<int> noDrop;
for (int i = ; i < k; ++i) grid[hits[i][]][hits[i][]] -= ;
for (int i = ; i < n; ++i) {
if (grid[][i] == ) check(grid, , i, noDrop);
}
for (int i = k - ; i >= ; --i) {
int oldSize = noDrop.size(), x = hits[i][], y = hits[i][];
if (++grid[x][y] != ) continue;
if ((x - >= && noDrop.count((x - ) * n + y))
|| (x + < m && noDrop.count((x + ) * n + y))
|| (y - >= && noDrop.count(x * n + y - ))
|| (y + < n && noDrop.count(x * n + y + ))
|| x == ) {
check(grid, x, y, noDrop);
res[i] = noDrop.size() - oldSize - ;
}
}
return res;
}
void check(vector<vector<int>>& grid, int i, int j, unordered_set<int>& noDrop) {
int m = grid.size(), n = grid[].size();
if (i < || i >= m || j < || j >= n || grid[i][j] != || noDrop.count(i * n + j)) return;
noDrop.insert(i * n + j);
check(grid, i - , j, noDrop);
check(grid, i + , j, noDrop);
check(grid, i, j - , noDrop);
check(grid, i, j + , noDrop);
}
};
我们再来看一种使用并查集Union Find的方法来做的,接触过并查集题目的童鞋应该有印象,是专门处理群组问题的一种算法。最典型的就是岛屿问题(例如Number of Islands II),很适合使用Union Find来做,LeetCode中有很多道可以使用这个方法来做的题,比如Friend Circles,Graph Valid Tree,Number of Connected Components in an Undirected Graph,和Redundant Connection等等。都是要用一个root数组,每个点开始初始化为不同的值,如果两个点属于相同的组,就将其中一个点的root值赋值为另一个点的位置,这样只要是相同组里的两点,通过find函数得到相同的值。当然初始化的时候也不用都赋为不同的值,如果表示的是坐标的话,我们也可以都初始化为-1,在find函数稍稍改动一下,也是可以的,这里就把判断 root[x] == x 改为 root[x] == -1 即可。这道题要稍稍复杂一些,我们不光需要并查集数组root,还需要知道每个位置右方和下方跟其相连的砖头个数数组count,还有标记每个位置是否相连且不会坠落的状态数组t,第一行各个位置的t值初始化为1。跟上面的方法类似,我们还是从最后一个砖头开始往回加,那么我们还是要把hits中所有的位置在grid中对应的值减1。然后我们要建立并查集的关系,我们遍历每一个位置,如果是砖头,那么我们对其右边和下边的位置进行检测,如果是砖头,我们就进行经典的并查集的操作,分别对当前位置和右边位置调用find函数,如果两个值不同,说明目前属于两个不同的群组,我们要链接上这两个位置,这里有个小问题需要注意一下,一般来说,我们链接群组的时候,root[x] = y 或 root[y] = x 都是可以的,但是这里我们需要使用第二种,为了跟后面的 count[x] += count[y] 对应起来,因为这里的y是在x的右边,所以count[x]要大于count[y],这里x和y我们都使用x的群组号,这样能保证后面加到正确的相连的砖头个数。还有我们的t[x] 和 t[y] 也需要更新,因为两个位置要相连,所以只有其中有一方是跟顶端相连的,那么二者的t值都应该为1。初始化完成后,我们就从hits数组末尾开始往回加砖头,跟之前的方法一样,首先要判断之前是有砖头的,然后遍历周围四个新位置,如果位置合法且有砖头的话,再调用并查集的经典操作,对老位置和新位置分别调用find函数,如果不在同一个群组的话,我们需要一个变量cnt来记录可以掉落的砖头个数,如果新位置的t值为0,说明其除了当前位置之外不跟其他位置相连,我们将其count值加入cnt。然后就是链接两个群组,通知更新老位置的count值,新老位置的t值等等。当周围位置遍历完成后,如果当前位置的t值为1,则将cnt值存入结果res的对应位置,参见代码如下:
解法二:
class Solution {
public:
vector<int> hitBricks(vector<vector<int>>& grid, vector<vector<int>>& hits) {
int m = grid.size(), n = grid[].size(), k = hits.size();
vector<int> res(k), root(m * n, -), count(m * n, ), t(m * n, );
vector<vector<int>> dirs{{-, }, {, }, {, }, {, -}};
for (int i = ; i < k; ++i) grid[hits[i][]][hits[i][]] -= ;
for (int i = ; i < n; ++i) t[i] = ;
for (int i = ; i < m; ++i) {
for (int j = ; j < n; ++j) {
if (grid[i][j] != ) continue;
if (i + < m && grid[i + ][j] == ) {
int x = find(root, i * n + j), y = find(root, (i + ) * n + j);
if (x != y) {root[y] = x; count[x] += count[y]; t[x] = t[y] = (t[x] | t[y]);}
}
if (j + < n && grid[i][j + ] == ) {
int x = find(root, i * n + j), y = find(root, i * n + j + );
if (x != y) {root[y] = x; count[x] += count[y]; t[x] = t[y] = (t[x] | t[y]);}
}
}
}
for (int i = k - ; i >= ; --i) {
int x = hits[i][], y = hits[i][], a = find(root, x * n + y), cnt = ;
if (++grid[x][y] != ) continue;
for (auto dir : dirs) {
int newX = x + dir[], newY = y + dir[];
if (newX < || newX >= m || newY < || newY >= n || grid[newX][newY] != ) continue;
int b = find(root, newX * n + newY);
if (a == b) continue;
if (!t[b]) cnt += count[b];
root[b] = a; count[a] += count[b]; t[a] = t[b] = (t[a] | t[b]);
}
if (t[a]) res[i] = cnt;
}
return res;
}
int find(vector<int>& root, int x) {
return (root[x] == -) ? x : find(root, root[x]);
}
};
参考资料:
https://leetcode.com/problems/bricks-falling-when-hit/
LeetCode All in One 题目讲解汇总(持续更新中...)
[LeetCode] Bricks Falling When Hit 碰撞时砖头掉落的更多相关文章
- [LeetCode] 803. Bricks Falling When Hit 打击砖块掉落
We have a grid of 1s and 0s; the 1s in a cell represent bricks. A brick will not drop if and only i ...
- [Swift]LeetCode803. 打砖块 | Bricks Falling When Hit
We have a grid of 1s and 0s; the 1s in a cell represent bricks. A brick will not drop if and only i ...
- 【LeetCode】362. Design Hit Counter 解题报告 (C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 字典 日期 题目地址:https://leetcode ...
- leetcode 699. Falling Squares 线段树的实现
线段树实现.很多细节值得品味 都在注释里面了 class SegTree: def __init__(self,N,query_fn,update_fn): self.tree=[0]*(2*N+2) ...
- leetcode 学习心得 (4)
645. Set Mismatch The set S originally contains numbers from 1 to n. But unfortunately, due to the d ...
- Swift LeetCode 目录 | Catalog
请点击页面左上角 -> Fork me on Github 或直接访问本项目Github地址:LeetCode Solution by Swift 说明:题目中含有$符号则为付费题目. 如 ...
- All LeetCode Questions List 题目汇总
All LeetCode Questions List(Part of Answers, still updating) 题目汇总及部分答案(持续更新中) Leetcode problems clas ...
- 【LeetCode】并查集 union-find(共16题)
链接:https://leetcode.com/tag/union-find/ [128]Longest Consecutive Sequence (2018年11月22日,开始解决hard题) 给 ...
- leetcode 76 dp& 强连通分量&并查集经典操作
800. Similar RGB Color class Solution { int getn(int k){ return (k+8)/17; } string strd(int k){ char ...
随机推荐
- DirectX11 With Windows SDK--08 Direct2D与Direct3D互操作性以及利用DWrite显示文字
前言 注意:从这一章起到后面的所有项目无一例外都利用了Direct2D与Direct3D互操作性,但系统要求为Win10, Win8.x 或 Win7 SP1且安装了KB2670838补丁以支持Dir ...
- Linux 动态加载共享库
- 别人的渗透测试(三)--SQL显错注入
续上一章. 安全狗拦下7成的人,过狗是门学问,偷笑.jpg.很感谢和https://home.cnblogs.com/u/xishaonian/ 博主能一起研究过狗. 说多了,言归正传SQL注入大显错 ...
- day 16 - 1 内置函数(二)
内置函数(二) reversed() 返回一个反向的迭代器 k = [1,2,3,4,5] k.reverse() #反转 print(k) k = [1,2,3,4,5] k2 = reverse ...
- Window10系统中MongoDB数据库导入数据文件
首先进入C:\Program Files\MongoDB\Server\4.0\bin> 打开cmd 创建一个空的数据库集合 db.createCollection("myColl ...
- TCP/IP(五)传输层之细说TCP的三次握手和四次挥手
前言 这一篇我将介绍的是大家面试经常被会问到的,三次握手四次挥手的过程.以前我听到这个是什么意思呀?听的我一脸蒙逼,但是学习之后就原来就那么回事! 一.运输层概述 1.1.运输层简介 这一层的功能也挺 ...
- LINUX系统VMSTAT命令详解
linux系统vmstat命令详解 [转自 https://www.cnblogs.com/wensiyang0916/p/6514820.html] vmstat 1 1表示每秒采集一次vms ...
- canvas图片与img图片的相互转换
最近在一个项目中,遇到了一个问题,需要把生成的canvas形式的二维码转换为图片,可以长按识别,保存等.查找了一些资料归纳总结了一些知识. 默认在jq库里进行,引入jquery.qrcode.min. ...
- Java框架中Struts框架的优缺点
Struts 优缺点优点:1. 实现 MVC 模式,结构清晰,使开发者只关注业务逻辑的实现.2.有丰富的 tag 可以用 ,Struts 的标记库(Taglib),如能灵活动用,则能大大提高开发效率3 ...
- js小方法,获取知道公历生日 (‘1992-01-19’),获取阴历生日日期,属相,非简单根据年份判断-----------声明:整理自网络!!
let lunar = { tg: '甲乙丙丁戊己庚辛壬癸', dz: '子丑寅卯辰巳午未申酉戌亥', number: '一二三四五六七八九十', year: '鼠牛虎兔龙蛇马羊猴鸡狗猪', mont ...