Given a non-empty 2D array grid of 0's and 1's, an island is a group of 1's (representing land) connected 4-directionally (horizontal or vertical.) You may assume all four edges of the grid are surrounded by water.
Count the number of distinct islands. An island is considered to be the same as another if they have the same shape, or have the same shape after rotation (90, 180, or 270 degrees only) or reflection (left/right direction or up/down direction).
Example 1:
11000 10000 00001 00011
Given the above grid map, return 1.
Notice that:
11 1
and
1 11
are considered same island shapes. Because if we make a 180 degrees clockwise rotation on the first island, then two islands will have the same shapes.
Example 2:
11100 10001 01001 01110
Given the above grid map, return 2.
Here are the two distinct islands:
111 1
and
1 1
Notice that:
111 1
and
1 111
are considered same island shapes. Because if we flip the first array in the up/down direction, then they have the same shapes.
Note: The length of each dimension in the given grid does not exceed 50.
這道題是之前那道 Number of Distinct Islands 的拓展,之前那道題定義的相同的島嶼必須是形狀完全相同的,而這里島嶼則可以進行 90 度旋轉,或者是水平豎直方向的鏡面對稱,這樣就極大的增加了難度,因為在判斷兩個形狀是否屬於一個島嶼的時候,要考慮各種各樣的情況,十分的復雜。這道題最大的難點也就是在於如何對同一種島嶼形狀進行建模,使得其所有翻轉或者對稱的島嶼形狀可以歸屬到同一類中。在之前那道 Number of Distinct Islands 中,由於是有平移操作,使用的 trick 是利用相對位移保持不變的特性來確定同一種同一種島嶼形狀的,在這道題中顯然不適用了,因為這里各種變換會破壞相對位移。那該怎么辦呢?比如現在有幾個長度相同的數組,是亂序的,怎么才能知道這些數組是否可以通過排序變成相同的數組,則最簡單的方法就是對所有的數組進行排序,排序后若相同,則表示屬於同一類,那么這個排序后的數組其實就可以代表所有同一種數組。借鑒這種思路,其實要做的就是找到一種建模方式,將所有屬於同一類的島嶼形狀都轉為其唯一的結構,然后放到一個有去處重復功能的集合中,最后集合中元素的個數即為不同島嶼的個數。
大體思路有了,再來看具體該如何實施。首先還是要找出每個單獨的小島,即找出所有相連的1,這跟之前的題目的做法相同,可以使用 DFS 或者 BFS 來找,這里先使用 DFS 來做。方法是遍歷 grid 數組,當遇到為1的位置,說明遇到小島了,對該位置調用遞歸函數,將和其相連的所有位置都存入到 shape 數組中。遞歸數組結束后,就有了該小島所有位置的坐標,現在就要對其進行轉換,或者叫 normalize,目的是變換成同一種島嶼形狀的唯一表示。對於每一個坐標位置,其實總共有8種變換可能,x和y都可以分別變為相反數,且也可以同時變為相反數,再加上其本身,這就4種了。同時,上面的每一種情況都可以交換x和y的位置,這樣總共就有8中變換方法,每一種情況都要考慮,所以使用8個大小相同的數組來保存各自所有的點。對於每一種形狀,都對其所有的點進行排序,然后將每個點的絕對坐標變為相對坐標,即每個點都減去排序后第一個點的坐標,最后再對這8個形狀排個序。這樣一頓操作猛如虎之后,只要是屬於同一個島嶼的形狀,最后都會得到一模一樣的8個形狀數組,為了簡單起見,只選取第一個形狀形狀當作這同一種島嶼的模型代表,並將其加入那個可以去重復的集合中即可,參見代碼如下:
解法一:
class Solution { public: int numDistinctIslands2(vector<vector<int>>& grid) { int m = grid.size(), n = grid[0].size(); set<vector<pair<int, int>>> st; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (grid[i][j]) { vector<pair<int, int>> shape; helper(grid, i, j, shape); st.insert(normalize(shape)); } } } return st.size(); } void helper(vector<vector<int>>& grid, int x, int y, vector<pair<int, int>>& shape) { if (x < 0 || x >= grid.size() || y < 0 || y >= grid[0].size()) return; if (grid[x][y] == 0) return; grid[x][y] = 0; shape.push_back({x, y}); helper(grid, x + 1, y, shape); helper(grid, x - 1, y, shape); helper(grid, x, y + 1, shape); helper(grid, x, y - 1, shape); } vector<pair<int, int>> normalize(vector<pair<int, int>>& shape) { vector<vector<pair<int, int>>> shapes(8); for (auto &a : shape) { int x = a.first, y = a.second; shapes[0].push_back({x, y}); shapes[1].push_back({x, -y}); shapes[2].push_back({-x, y}); shapes[3].push_back({-x, -y}); shapes[4].push_back({y, x}); shapes[5].push_back({y, -x}); shapes[6].push_back({-y, x}); shapes[7].push_back({-y, -x}); } for (auto &a : shapes) { sort(a.begin(), a.end()); for (int i = (int)shape.size() - 1; i >= 0; --i) { a[i].first -= a[0].first; a[i].second -= a[0].second; } } sort(shapes.begin(), shapes.end()); return shapes[0]; } };
下面這種寫法是 BFS 形式的,大體思路相同,就是在找每個島嶼的時候使用的是迭代的寫法,找出了每個島嶼位置集合 shape 之后,進行 normalize 操作跟上面完全相同,最后還是看去重復集合中元素的個數即可,參見代碼如下:
解法二:
class Solution { public: int numDistinctIslands2(vector<vector<int>>& grid) { int m = grid.size(), n = grid[0].size(); set<vector<pair<int, int>>> st; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (grid[i][j] == 0) continue; grid[i][j] = 0; vector<pair<int, int>> shape{{i, j}}; int i = 0; while (i < shape.size()) { auto t = shape[i++]; int x = t.first, y = t.second; if (x > 0 && grid[x - 1][y] != 0) { grid[x - 1][y] = 0; shape.push_back({x - 1, y}); } if (x + 1 < m && grid[x + 1][y] != 0) { grid[x + 1][y] = 0; shape.push_back({x + 1, y}); } if (y > 0 && grid[x][y - 1] != 0) { grid[x][y - 1] = 0; shape.push_back({x, y - 1}); } if (y + 1 < n && grid[x][y + 1] != 0) { grid[x][y + 1] = 0; shape.push_back({x, y + 1}); } } st.insert(normalize(shape)); } } return st.size(); } vector<pair<int, int>> normalize(vector<pair<int, int>>& shape) { vector<vector<pair<int, int>>> shapes(8); for (auto &a : shape) { int x = a.first, y = a.second; shapes[0].push_back({x, y}); shapes[1].push_back({x, -y}); shapes[2].push_back({-x, y}); shapes[3].push_back({-x, -y}); shapes[4].push_back({y, x}); shapes[5].push_back({y, -x}); shapes[6].push_back({-y, x}); shapes[7].push_back({-y, -x}); } for (auto &a : shapes) { sort(a.begin(), a.end()); for (int i = (int)shape.size() - 1; i >= 0; --i) { a[i].first -= a[0].first; a[i].second -= a[0].second; } } sort(shapes.begin(), shapes.end()); return shapes[0]; } };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/711
類似題目:
參考資料:
https://leetcode.com/problems/number-of-distinct-islands-ii/
https://leetcode.com/problems/number-of-distinct-islands-ii/discuss/231300/C%2B%2B-BFSDFS
