[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.html

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:app

  • 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,不表明題會作,其實這道題仍是蠻有難度的,花了博主很長的時間。ide

首先咱們來想,咱們確定要統計出當前沒有掉落的磚頭數量,當去掉某個磚頭後,咱們能夠統計當前還連着的磚頭數量,兩者作差值就是掉落的磚頭數量。那麼如何來統計不會掉落的磚頭數量呢,因爲頂層的磚頭時不會掉落的,那麼跟頂層相連的全部磚頭確定也不會掉落,咱們就可使用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的緣由是去掉的磚頭不算掉落的磚頭數中,參見代碼以下:spa

 

解法一:code

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的對應位置,參見代碼以下:htm

 

解法二:blog

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 題目講解彙總(持續更新中...)

相關文章
相關標籤/搜索