题目地址:
https://leetcode.com/problems/number-of-distinct-islands-ii/
给定一个 m m m行 n n n列的 0 − 1 0-1 0−1矩阵,每个 1 1 1连通块视为一个岛屿。这里的连通是四连通。两个岛屿相同当且仅当其中一个可以通过旋转 0 ° , 9 0 ° , 18 0 ° , 27 0 ° 0^\degree,90^\degree,180^\degree,270^\degree 0°,90°,180°,270°或者镜面翻转后,与另一个岛屿重合。问一共有多少个不同的岛屿。
可以用哈希。这里需要保证两个相同形状的岛屿哈希值一样。我们可以采用这样的哈希方式,设某个岛屿的所有 1 1 1的坐标是 ( a [ i ] x , a [ i ] y ) (a[i]_x,a[i]_y) (a[i]x,a[i]y),其中 0 ≤ i < k 0\le i<k 0≤i<k,那么令其哈希值为: ∑ i < j ( a [ i ] x − a [ j ] x ) 2 + ( a [ i ] y − a [ j ] y ) 2 \sum_{i<j}\sqrt{(a[i]_x-a[j]_x)^2+(a[i]_y-a[j]_y)^2} i<j∑(a[i]x−a[j]x)2+(a[i]y−a[j]y)2这样显然可以保证相同形状的岛屿哈希值一样。值得注意的是,上面的公式里,如果采用曼哈顿距离,或者去掉根号,得到的哈希值冲突几率较大。这里还是哈希成浮点数,冲突概率很低。但是数岛屿个数的时候,就要比较浮点数了,此时无法用哈希表,而要逐个比较。代码如下:
class Solution {
public:
using PII = pair<int, int>;
int numDistinctIslands2(vector<vector<int>>& g) {
const double eps = 1e-10;
int m = g.size(), n = g[0].size();
vector<double> hashs;
vector<PII> v;
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
if (g[i][j] == 1) {
v.clear();
dfs(i, j, v, g);
double h = get_hash(v);
bool found = false;
for (double x : hashs)
if (abs(x - h) < eps) {
found = true;
break;
}
if (!found) hashs.push_back(h);
}
return hashs.size();
}
double get_hash(vector<PII>& v) {
double h = 0;
for (int i = 0; i < v.size(); i++)
for (int j = i + 1; j < v.size(); j++) {
auto [x1, y1] = v[i];
auto [x2, y2] = v[j];
int dx = x1 - x2, dy = y1 - y2;
h += sqrt(dx * dx + dy * dy);
}
return h;
}
void dfs(int x, int y, vector<PII>& v, vector<vector<int>>& g) {
g[x][y] = 0;
v.push_back({x, y});
static int d[] = {-1, 0, 1, 0, -1};
for (int i = 0; i < 4; i++) {
int nx = x + d[i], ny = y + d[i + 1];
if (0 <= nx && nx < g.size() && 0 <= ny && ny < g[0].size() &&
g[nx][ny] == 1)
dfs(nx, ny, v, g);
}
}
};
时间 O ( m n + ∑ s 2 ) O(mn + \sum s^2) O(mn+∑s2), s s s为每个岛屿的size,空间 O ( m n ) O(mn) O(mn)。