我们有一组包含1和0的网格;其中1表示砖块。 当且仅当一块砖直接连接到网格的顶部,或者它至少有一块相邻(4 个方向之一)砖块不会掉落时,它才不会落下。
我们会依次消除一些砖块。每当我们消除 (i, j) 位置时, 对应位置的砖块(若存在)会消失,然后其他的砖块可能因为这个消除而落下。
返回一个数组表示每次消除操作对应落下的砖块数目。
示例 1:
输入:
grid = [[1,0,0,0],[1,1,1,0]]
hits = [[1,0]]
输出: [2]
解释:
如果我们消除(1, 0)位置的砖块, 在(1, 1) 和(1, 2) 的砖块会落下。所以我们应该返回2。
示例 2:
输入:
grid = [[1,0,0,0],[1,1,0,0]]
hits = [[1,1],[1,0]]
输出:[0,0]
解释:
当我们消除(1, 0)的砖块时,(1, 1)的砖块已经由于上一步消除而消失了。所以每次消除操作不会造成砖块落下。注意(1, 0)砖块不会记作落下的砖块。
注意:
网格的行数和列数的范围是[1, 200]。
消除的数字不会超过网格的区域。
可以保证每次的消除都不相同,并且位于网格的内部。
一个消除的位置可能没有砖块,如果这样的话,就不会有砖块落下。
思 路 分 析 : \color{blue}思路分析: 思路分析:对于这道题,我的第一想法是使用深度优先搜索法,每当我们进行hit时,我们使用dfs判断这敲击的砖的四周是否能够产生掉落,如果会产生掉落,则使用dfs进行计算掉落。
class Solution {
public:
vector<pair<int, int>> directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};//上下左右四个方向
vector<int> hitBricks(vector<vector<int>>& grid, vector<vector<int>>& hits) {
int hitsSize = hits.size();
vector<int> resVec(hitsSize, 0);//resVec[i]用于记录每次敲击掉落的砖块数
int rowSize = grid.size(), colSize = grid[0].size();
//顺序敲击hits数组
for (int i = 0; i < hitsSize; ++i){
if (grid[hits[i][0]][hits[i][1]] != 0){//只有当这个敲击的位置游砖块时才有可能掉下砖块
grid[hits[i][0]][hits[i][1]] = 0;//进行敲击,破碎
vector<vector<bool>> visited(rowSize, vector<bool>(colSize, false));
for (auto &direction : directions){//上下左右四个方向确定是否出现掉落
int nextRow = hits[i][0] + direction.first;
int nextCol = hits[i][1] + direction.second;
if (nextRow < 0 || nextRow >= rowSize || nextCol < 0 || nextCol >= colSize || !grid[nextRow][nextCol]){
continue;
}
if (isFall(grid, visited, nextRow, nextCol)){//首先判断这个位置是否会产生掉落
resVec[i] += dfsHelper(grid, nextRow, nextCol);
}
}
}
}
return resVec;
}
//判断grid[row][col]这个位置是否会产生掉落
bool isFall(vector<vector<int>>& grid, vector<vector<bool>>& visited, int row, int col){
visited[row][col] = true;//标记访问
if (grid[row][col] == 0){//如果这个位置是0,没有砖块,说明与之相连的会掉落
return true;
}
if (row == 0){//只有到达了第0行,才不会掉落
return false;
}
int rowSize = grid.size(), colSize = grid[0].size();
for (auto &direction : directions){//四个方向进行寻找
int nextRow = row + direction.first;
int nextCol = col + direction.second;
if (nextRow < 0 || nextRow >= rowSize || nextCol < 0 || nextCol >= colSize || visited[nextRow][nextCol]){
continue;
}
else if (!isFall(grid, visited, nextRow, nextCol)){//如果有一个方向不会掉落,则一片区域都不会掉落
return false;
}
}
return true;
}
//掉落与grid[row][col]相邻的砖块,并且返回掉落的砖块数
int dfsHelper(vector<vector<int>>& grid, int row, int col){
int tempRes = 1;
grid[row][col] = 0;//下落
int rowSize = grid.size(), colSize = grid[0].size();
for (auto &direction : directions){//掉落grid[row][col]四个方向的砖块
int nextRow = row + direction.first;
int nextCol = col + direction.second;
if (nextRow < 0 || nextRow >= rowSize || nextCol < 0 || nextCol >= colSize || !grid[nextRow][nextCol]){
continue;
}
else {
tempRes += dfsHelper(grid, nextRow, nextCol);
}
}
return tempRes;
}
};
class Solution {
public:
vector<pair<int, int>> directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};//上下左右四个方向
vector<int> hitBricks(vector<vector<int>>& grid, vector<vector<int>>& hits) {
int hitsSize = hits.size();
vector<int> resVec(hitsSize, 0);//resVec[i]用于记录每次敲击掉落的砖块数
int rowSize = grid.size(), colSize = grid[0].size();
//顺序敲击hits数组
for (int i = 0; i < hitsSize; ++i){
if (grid[hits[i][0]][hits[i][1]] != 0){//只有当这个敲击的位置游砖块时才有可能掉下砖块
grid[hits[i][0]][hits[i][1]] = 0;//进行敲击,破碎
for (auto &direction : directions){//上下左右四个方向确定是否出现掉落
int nextRow = hits[i][0] + direction.first;
int nextCol = hits[i][1] + direction.second;
if (nextRow < 0 || nextRow >= rowSize || nextCol < 0 || nextCol >= colSize || !grid[nextRow][nextCol]){
continue;
}
vector<vector<bool>> visited(rowSize, vector<bool>(colSize, false));
if (isFall(grid, visited, nextRow, nextCol)){//首先判断这个位置是否会产生掉落
resVec[i] += dfsHelper(grid, nextRow, nextCol);
}
}
}
}
return resVec;
}
//判断grid[row][col]这个位置是否会产生掉落
bool isFall(vector<vector<int>>& grid, vector<vector<bool>>& visited, int row, int col){
if (row == 0){
return false;
}
queue<pair<int, int>> myQue;//广度优先搜索辅助队列
myQue.push({row, col});
visited[row][col] = true;//标记访问
int rowSize = grid.size(), colSize = grid[0].size();
while (!myQue.empty()){
pair<int, int> front = myQue.front();
myQue.pop();
for (auto &direction : directions){//掉落grid[row][col]四个方向的砖块
int nextRow = front.first + direction.first;
int nextCol = front.second + direction.second;
if (nextRow < 0 || nextRow >= rowSize || nextCol < 0 || nextCol >= colSize || visited[nextRow][nextCol]){
continue;
}
if (nextRow == 0){//遇到了顶层
return false;
}
visited[nextRow][nextCol] = true;
myQue.push({nextRow, nextCol});
}
}
return true;
}
//掉落与grid[row][col]相邻的砖块,并且返回掉落的砖块数
int bfsHelper(vector<vector<int>>& grid, int row, int col){
int tempRes = 1;
grid[row][col] = 0;
queue<pair<int, int>> myQue;//广度优先搜索辅助队列
myQue.push({row, col});
int rowSize = grid.size(), colSize = grid[0].size();
while (!myQue.empty()){
pair<int, int> front = myQue.front();
myQue.pop();
for (auto &direction : directions){//掉落grid[row][col]四个方向的砖块
int nextRow = front.first + direction.first;
int nextCol = front.second + direction.second;
if (nextRow < 0 || nextRow >= rowSize || nextCol < 0 || nextCol >= colSize || !grid[nextRow][nextCol]){
continue;
}
tempRes += 1;
grid[nextRow][nextCol] = 0;
myQue.push({nextRow, nextCol});
}
}
return tempRes;
}
};
还是超时了。。。
经过查找资料后,发现一种逆向思维的方法。
我们现在先想一个问题,如果我们把hits数组的每一个即将敲击的位置都进行减一操作,grid中剩下的 == 1的砖块中,如果某个砖块能直接、间接与顶层相连,说明这个砖块无论你怎们敲击hits数组中的位置,它都不会掉落。我们用一个set容器nowRemain记录这些不会掉落的砖块位置信息。
我们还得明白一个道理,如果敲击某个为空的位置,它一定不会产生掉落。
接着我们逆向操作,从后往前逐渐进行+1还原hits数组,每次复原一个hit的位置,如果这个hit的位置是砖块(敲击它能可能产生砖块掉落),并且它是在顶层或者周边的某个位置在nowRemain容器中,则说明 这个hit的位置在hit前不会掉落,那么hit了它之后就可能在它四周产生了砖块掉落,因此我们需要将它四周的不在nowRemain容器中的砖块进行复原。那么复原了多少个位置就是敲击这个砖块会掉落的砖块个数。
class Solution {
public:
vector<int> hitBricks(vector<vector<int>>& grid, vector<vector<int>>& hits) {
set<pair<int,int>> nowRemain;//存放当前状态不掉落的砖块信息
int hitsSize = (int)hits.size();
vector<int> result(hitsSize, 0);//存储结果
int rowSize = (int)grid.size(), colSize = (int)grid[0].size();
//第一步:将所有即将敲击的位置进行减1操作,让它减一而不是变为零,是因为可能hit了多次,只有还原最开始的那一次hit之后才能将这个砖块恢复
for (int i = 0; i < hitsSize; ++i) {
--grid[hits[i][0]][hits[i][1]];
}
//第二步:将现在所有与顶层有直接、间接连接的砖块恢复(因为第一步去除了所有敲击的位置的砖块,所以当所有敲击完成后,这些与与顶层有直接、间接连接的砖块仍然不会掉落
for (int col = 0; col < colSize; ++col) {
if(grid[0][col] == 1) {//顶层第col个位置
findRemain(grid, 0, col, nowRemain);
}
}
//第三步:从最后一次hit开始,逐步恢复砖块,并且找到因为hit当前砖块而掉落的砖块,将其加入remain中
for (int k = hitsSize - 1; k >= 0; --k) {
int row = hits[k][0];//获取敲击的位置
int col = hits[k][1];
grid[row][col] += 1;//在第一步的时候是减一,这里进行加1逆操作
if(grid[row][col] == 1) {//grid[row][col] != 1,说明这个位置hit了多次,还没有到最开始的那个hit,所以不能恢复
//注意我们是从后往前逆恢复,当前remain的大小是敲击了当前位置后剩余的大小
int afterHitRemainSize = (int)nowRemain.size();//敲击当前砖块后的剩余砖块数(
//我们现在需要判断grid[row][col]是否是能够不掉落(只有当grid[row][col]在顶层,或者上下左右存在不掉落的砖块,则grid[row][col]也能够不掉落
//如果当前hit的砖块不能固定,说明在hit前它已经掉落了,那么就不应该搜索恢复它周围的砖块,因为它们不是因为hit它而掉落的
if(row == 0 || (row > 0 && nowRemain.count({row - 1, col})) || (row < rowSize - 1 && nowRemain.count({row + 1, col}))
|| (col > 0 && nowRemain.count({row, col - 1})) || (col < colSize - 1 && nowRemain.count({row, col + 1}))){
findRemain(grid, row , col, nowRemain);//开始恢复grid[row][col]及其直接、间接相连的砖块
//现在remain的大小是敲击grid[row][col]之前剩余的砖块数,而afterHitRemainSize是敲击之后剩余的砖块数
result[k] = (int)nowRemain.size() - afterHitRemainSize - 1; //grid[row][col]这个砖块是敲击掉的,不算在掉落砖块数内
}
}
}
return result;
}
//在grid[row][col]不掉落的前提下,我们将它上下左右四个方向的砖进行恢复
void findRemain(vector<vector<int>>& grid, int row, int col, set<pair<int,int>>& nowRemain) {
int rowSize = (int)grid.size(), colSize = (int)grid[0].size();
//如果这grid[row][col]不是砖块,则不是恢复的前提,h如果这个位置已经在remain中,说明这块砖已经恢复了
if(grid[row][col] != 1 || nowRemain.count({row,col})) {
return;
}
nowRemain.insert({row, col});//插入到nowRemain中(表示恢复
//分别恢复它的上下左右四个方向
if(row > 0) {
findRemain(grid, row - 1, col, nowRemain);
}
if(row < rowSize - 1) {
findRemain(grid, row + 1, col, nowRemain);
}
if(col > 0) {
findRemain(grid, row, col - 1, nowRemain);
}
if(col < colSize - 1) {
findRemain(grid, row, col + 1, nowRemain);
}
}
};