LeetCode 打砖块(逆向思维)

我们有一组包含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;
    }
};

LeetCode 打砖块(逆向思维)_第1张图片
这个时候,我本不死心,采用广度优先搜索进行尝试。

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);
        }
    }
};

LeetCode 打砖块(逆向思维)_第2张图片
这道题采用逆向思维,确实非常巧妙。

你可能感兴趣的:(LeetCode,图,逆向思维,递归)