1. 程式人生 > >[LeetCode] Number of Islands II 島嶼的數量之二

[LeetCode] Number of Islands II 島嶼的數量之二

A 2d grid map of m rows and n columns is initially filled with water. We may perform an addLand operation which turns the water at position (row, col) into a land. Given a list of positions to operate, count the number of islands after each addLand operation. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.

Example:

Given m = 3, n = 3positions = [[0,0], [0,1], [1,2], [2,1]].
Initially, the 2d grid grid is filled with water. (Assume 0 represents water and 1 represents land).

0 0 0
0 0 0
0 0 0

Operation #1: addLand(0, 0) turns the water at grid[0][0] into a land.

1 0 0
0 0 0   Number of islands = 1
0 0 0

Operation #2: addLand(0, 1) turns the water at grid[0][1] into a land.

1 1 0
0 0 0   Number of islands = 1
0 0 0

Operation #3: addLand(1, 2) turns the water at grid[1][2] into a land.

1 1 0
0 0 1   Number of islands = 2
0 0 0

Operation #4: addLand(2, 1) turns the water at grid[2][1] into a land.

1 1 0
0 0 1   Number of islands = 3
0 1 0

We return the result as an array: [1, 1, 2, 3]

Challenge:

Can you do it in time complexity O(k log mn), where k is the length of the positions?

這道題是之前那道Number of Islands的拓展,難度增加了不少,因為這次是一個點一個點的增加,每增加一個點,都要統一一下現在總共的島嶼個數,最開始初始化時沒有陸地,如下:

0 0 0
0 0 0
0 0 0

假如我們在(0, 0)的位置增加一個陸地,那麼此時島嶼數量為1:

1 0 0
0 0 0
0 0 0

假如我們再在(0, 2)的位置增加一個陸地,那麼此時島嶼數量為2:

1 0 1
0 0 0
0 0 0

假如我們再在(0, 1)的位置增加一個陸地,那麼此時島嶼數量卻又變為1:

1 1 1
0 0 0
0 0 0

假如我們再在(1, 1)的位置增加一個陸地,那麼此時島嶼數量仍為1:

1 1 1
0 1 0
0 0 0

那麼我們為了解決這種陸地之間會合並的情況,最好能夠將每個陸地都標記出其屬於哪個島嶼,這樣就會方便我們統計島嶼個數。這種群組類問題,很適合使用聯合查詢 Union Find 來做,又叫並查集 Disjoint Set,LeetCode中使用這種解法的題目還不少呢,比如Friend CirclesGraph Valid TreeRedundant Connection II 等等。一般來說,UF演算法的思路是每個個體先初始化為不同的群組,然後遍歷有關聯的兩個個體,如果發現其getRoot函式的返回值不同,則手動將二者加入一個群組,然後總群組數自減1。這裡就要分別說一下root陣列,和getRoot函式。兩個同群組的個體,通過getRoot函式一定會返回相同的值,但是其在root 陣列中的值不一定相同,我們可以類比成getRoot函式返回的是祖先,如果兩個人的祖先相同,那麼其是屬於一個家族的(這裡不是指人類共同的祖先哈)。root可以用陣列或者HashMap來表示,如果個體是數字的話,那麼陣列就OK,如果個體是字串的話,可能就需要用HashMap了。root陣列的初始化可以有兩種,可以均初始化為-1,或者都初始化為不同的數字,博主一般喜歡初始化為不同的數字。getRoot函式的寫法也可用遞迴或者迭代的方式,可參見博主之前的帖子Redundant Connection II中的討論部分。這麼一說感覺UF演算法的東西還蠻多的,啥時候博主寫個UF總結貼吧。

那麼具體來看這道題吧,此題跟經典的UF使用場景有一點點的區別,因為一般的場景中兩個個體之間只有兩種關係,屬於一個群組或者不屬於同一個群組,而這道題裡面由於water的存在,就多了一種情況,我們只需要事先檢測一下當前位置是不是島嶼就行了,總之問題不大。一般來說我們的root陣列都是使用一維陣列,方便一些,那麼這裡就可以將二維陣列encode為一維的,於是我們需要一個長度為m*n的一維陣列來標記各個位置屬於哪個島嶼,我們假設每個位置都是一個單獨島嶼,島嶼編號可以用其座標位置表示,但是我們初始化時將其都賦為-1,這樣方便我們知道哪些位置尚未變成島嶼。然後我們開始遍歷陸地陣列,將其島嶼編號設定為其座標位置,然後島嶼計數加1,我們此時開始遍歷其上下左右的位置,遇到越界或者島嶼標號為-1的情況直接跳過,現在知道我們初始化為-1的好處了吧,遇到是water的地方直接跳過。否則我們用getRoot來查詢鄰居位置的島嶼編號,同時也用getRoot來查詢當前點的編號,這一步就是經典的UF演算法的操作了,因為當前這兩個land是相鄰的,它們是屬於一個島嶼,所以其getRoot函式的返回值suppose應該是相等的,但是如果返回值不同,說明我們需要合併島嶼,將兩個返回值建立關聯,並將島嶼計數cnt減1。當我們遍歷完當前點的所有鄰居時,該合併的都合併完了,將此時的島嶼計數cnt存入結果中,參見程式碼如下:

class Solution {
public:
    vector<int> numIslands2(int m, int n, vector<pair<int, int>>& positions) {
        vector<int> res;
        int cnt = 0;
        vector<int> roots(m * n, -1);
        vector<vector<int>> dirs{{0, -1}, {-1, 0}, {0, 1}, {1, 0}};
        for (auto a : positions) {
            int id = n * a.first + a.second;
            if (roots[id] == -1) {
                roots[id] = id;
                ++cnt;
            }
            for (auto dir : dirs) {
                int x = a.first + dir[0], y = a.second + dir[1], cur_id = n * x + y;
                if (x < 0 || x >= m || y < 0 || y >= n || roots[cur_id] == -1) continue;
                int p = findRoot(roots, cur_id), q = findRoot(roots, id);
                if (p != q) {
                    roots[p] = q;
                    --cnt;
                }
            }
            res.push_back(cnt);
        }
        return res;
    }
    int findRoot(vector<int>& roots, int id) {
        return (id == roots[id]) ? id : findRoot(roots, roots[id]);
    }
};

類似題目:

參考資料: