[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[0].size(), k = hits.size();
        vector<int> res(k);
        unordered_set<int> noDrop;
        for (int i = 0; i < k; ++i) grid[hits[i][0]][hits[i][1]] -= 1;
        for (int i = 0; i < n; ++i) {
            if (grid[0][i] == 1) check(grid, 0, i, noDrop);
        }
        for (int i = k - 1; i >= 0; --i) {
            int oldSize = noDrop.size(), x = hits[i][0], y = hits[i][1];
            if (++grid[x][y] != 1) continue;
            if ((x - 1 >= 0 && noDrop.count((x - 1) * n + y)) 
                || (x + 1 < m && noDrop.count((x + 1) * n + y))
                || (y - 1 >= 0 && noDrop.count(x * n + y - 1))
                || (y + 1 < n && noDrop.count(x * n + y + 1))
                || x == 0) {
                check(grid, x, y, noDrop);
                res[i] = noDrop.size() - oldSize - 1;
            }
        }
        return res;
    }
    void check(vector<vector<int>>& grid, int i, int j, unordered_set<int>& noDrop) {
        int m = grid.size(), n = grid[0].size();
        if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] != 1 || noDrop.count(i * n + j)) return;
        noDrop.insert(i * n + j);
        check(grid, i - 1, j, noDrop);
        check(grid, i + 1, j, noDrop);
        check(grid, i, j - 1, noDrop);
        check(grid, i, j + 1, noDrop);
    }
};

 

我們再來看一種使用並查集Union Find的方法來做的,接觸過並查集題目的童鞋應該有印象,是專門處理群組問題的一種算法。最典型的就是島嶼問題(例如Number of Islands II),很適合使用Union Find來做,LeetCode中有很多道可以使用這個方法來做的題,比如Friend CirclesGraph Valid TreeNumber 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[0].size(), k = hits.size();
        vector<int> res(k), root(m * n, -1), count(m * n, 1), t(m * n, 0);
        vector<vector<int>> dirs{{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
        for (int i = 0; i < k; ++i) grid[hits[i][0]][hits[i][1]] -= 1;
        for (int i = 0; i < n; ++i) t[i] = 1;
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (grid[i][j] != 1) continue;
                if (i + 1 < m && grid[i + 1][j] == 1) {
                    int x = find(root, i * n + j), y = find(root, (i + 1) * n + j);
                    if (x != y) {root[y] = x; count[x] += count[y]; t[x] = t[y] = (t[x] | t[y]);}
                }
                if (j + 1 < n && grid[i][j + 1] == 1) {
                    int x = find(root, i * n + j), y = find(root, i * n + j + 1);
                    if (x != y) {root[y] = x; count[x] += count[y]; t[x] = t[y] = (t[x] | t[y]);}
                }
            }
        }
        for (int i = k - 1; i >= 0; --i) {
            int x = hits[i][0], y = hits[i][1], a = find(root, x * n + y), cnt = 0;
            if (++grid[x][y] != 1) continue;
            for (auto dir : dirs) {
                int newX = x + dir[0], newY = y + dir[1];
                if (newX < 0 || newX >= m || newY < 0 || newY >= n || grid[newX][newY] != 1) 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] == -1) ? x : find(root, root[x]);
    }
};

 

參考資料:

https://leetcode.com/problems/bricks-falling-when-hit/

https://leetcode.com/problems/bricks-falling-when-hit/discuss/173204/Java-DFS-solution-by-adding-bricks-reversely!!!

https://leetcode.com/problems/bricks-falling-when-hit/discuss/120259/C%2B%2B-reverse-adding-brick-with-union-O(colume*row%2Bhits)-time-and-space

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM