Graph_4_岛屿问题

  1. 岛屿问题
  2. 200.岛屿数量
  3. 1254.统计封闭岛屿的数目
  4. 1020.飞地的数量
  5. 695.岛屿的最大面积
  6. 1905.统计子岛屿
  7. 694.不同岛屿的数量
  8. 711.不同岛屿的数量 II

岛屿问题

1.岛屿类题目的本质是二维数组的遍历问题, 把二维数组可以看成是一种特殊的图, 遍历的时候有上下左右四个方向, 通常采用dfs进行遍历
2.遍历过程中首先维护一个方向数组

vector<vector<int>> dirs = {{-1,0}, {0,-1}, {1,0}, {0,1}}

表示可以变化的四个方向
3.遍历的时候首先判断是否越界, 然后判断是否已经遍历过, 因此用另一个 visited[i][j] 标记是否已经遍历过
4.遍历只需要去 dfs 邻居四个方向节点
5.岛屿题的主要框架为

// 方向数组
vector<vector<int>> dirs = {{-1,0}, {0,-1}, {1,0}, {0,1}};

void dfs(int i, int j, vector<vector<int>>& grid, vector<vector<int>>& visited) {
  int m = grid.size();
  int n = grid[0].size();
  if (i < 0 || j < 0 || i >= m || j >= n) {
    return;
  }
  if (visited[i][j]) {
    return;
  }
  visited[i][j] = true;
  for (vecotr<int>& d : dirs) {
    int neighborX = i + d[0];
    int neighborY = j + d[1];
    dfs(neighborX, neighborY, grid, visited);
  }
  return;
}

200.岛屿数量

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。

示例 1:
输入:grid = [
[“1”,”1”,”1”,”1”,”0”],
[“1”,”1”,”0”,”1”,”0”],
[“1”,”1”,”0”,”0”,”0”],
[“0”,”0”,”0”,”0”,”0”]
]
输出:1

示例 2:
输入:grid = [
[“1”,”1”,”0”,”0”,”0”],
[“1”,”1”,”0”,”0”,”0”],
[“0”,”0”,”1”,”0”,”0”],
[“0”,”0”,”0”,”1”,”1”]
]
输出:3

分析:
1.dfs 岛屿经典题目, 针对所有的位置进行遍历, 遍历到当前节点的时候, 岛屿数量+=1, dfs 它的四个方向, 如果还是岛屿那么标记成海洋避免重复计算

class Solution {
 private:
  int ans = 0;
 public:
  const vector<vector<int>> dirs = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
  void dfs(vector<vector<char>>& grid, int i, int j) {
    if (i < 0 || i >= grid.size() || j < 0 || j >= grid[0].size()) {
      return;
    }
    if (grid[i][j] == '0') {
      return;
    }
    if (grid[i][j] == '1') {
      grid[i][j] = '0';
      for (int k = 0; k < 4; ++k) {
        int nx = i + dirs[k][0];
        int ny = j + dirs[k][1];
        dfs(grid, nx, ny);
      }
    }
    return;
  }
  int numIslands(vector<vector<char>>& grid) {
    for (int i = 0; i < grid.size(); ++i) {
      for (int j = 0; j < grid[0].size(); ++j) {
        if (grid[i][j] == '1') {
          ++ans;
          dfs(grid, i, j);
        }
      }
    }
    return ans;
  }
};

1254.统计封闭岛屿的数目

二维矩阵 grid 由 0 (土地)和 1 (水)组成。岛是由最大的4个方向连通的 0 组成的群,封闭岛是一个 完全 由1包围(左、上、右、下)的岛。
请返回 封闭岛屿 的数目。

示例 1:输入:grid = [
[1,1,1,1,1,1,1,0],
[1,0,0,0,0,1,1,0],
[1,0,1,0,1,1,1,0],
[1,0,0,0,0,1,0,1],
[1,1,1,1,1,1,1,0]
]
输出:2
解释:灰色区域的岛屿是封闭岛屿,因为这座岛屿完全被水域包围(即被 1 区域包围)。

示例 2:输入:grid = [
[0,0,1,0,0],
[0,1,0,1,0],
[0,1,1,1,0]
] 输出:1

示例 3:
输入:grid = [
[1,1,1,1,1,1,1],
[1,0,0,0,0,0,1],
[1,0,1,1,1,0,1],
[1,0,1,0,1,0,1],
[1,0,1,1,1,0,1],
[1,0,0,0,0,0,1],
[1,1,1,1,1,1,1]
]
输出:2

分析:
1.题目中只统计封闭的岛屿, 也就是说需要把和四周相连的岛屿用水覆盖掉, 剩下的遍历并且 dfs 合并得到的岛屿就是封闭的岛屿了

class Solution {
 private:
  int m;
  int n;
  int count = 0;
  const vector<vector<int>> dirs = {{-1,0}, {0,-1}, {1,0}, {0,1}};
 public:
  void dfs(int i, int j, vector<vector<int>>& grid) {
    if (i < 0 || j < 0 || i >= m || j >= n)
      return;
    if (grid[i][j] == 1)
      return;
    grid[i][j] = 1;
    for (int k = 0; k < 4; ++k) {
      int neighborX = i + dirs[k][0];
      int neighborY = j + dirs[k][1];
      dfs(neighborX, neighborY, grid);
    }
    return;
  }
  int closedIsland(vector<vector<int>>& grid) {
    m = grid.size();
    if (m == 0)
      return count;
    n = grid[0].size();
    for (int i = 0; i < m; ++i) {
      dfs(i, 0, grid);
      dfs(i, n-1, grid);
    }
    for (int j = 0; j < n; ++j) {
      dfs(0, j, grid);
      dfs(m-1, j, grid);
    }
    for (int i = 0; i < m; ++i) {
      for (int j = 0; j < n; ++j) {
        if (grid[i][j] == 0) {
          ++count;
          dfs(i, j, grid);
        }
      }
    }
    return count;
  }
};

1020.飞地的数量

给你一个大小为 m x n 的二进制矩阵 grid ,其中 0 表示一个海洋单元格、1 表示一个陆地单元格。
一次 移动 是指从一个陆地单元格走到另一个相邻(上、下、左、右)的陆地单元格或跨过 grid 的边界。
返回网格中 无法 在任意次数的移动中离开网格边界的陆地单元格的数量。

示例 1:输入:grid = [
[0,0,0,0],
[1,0,1,0],
[0,1,1,0],
[0,0,0,0]
]
输出:3 解释:有三个 1 被 0 包围。一个 1 没有被包围,因为它在边界上。

示例 2:输入:grid = [
[0,1,1,0],
[0,0,1,0],
[0,0,1,0],
[0,0,0,0]
]
输出:0 解释:所有 1 都在边界上或可以到达边界。

分析:
1.封闭岛屿的加法求和翻版, 但要对封闭的区块求和, 求出封闭区域之后, 多加一步对结果进行求和

class Solution {
 public:
  int m;
  int n;
  int count = 0;
  const vector<vector<int>> dirs = {{-1,0}, {0,-1}, {1,0}, {0,1}};
  void dfs(int i, int j, vector<vector<int>>& grid) {
    if (i < 0 || j < 0 || i >= m || j >= n)
      return;
    if (grid[i][j] == 0)
      return;
    grid[i][j] = 0;
    for (int k = 0; k < 4; ++k) {
      int neighborX = i + dirs[k][0];
      int neighborY = j + dirs[k][1];
      dfs(neighborX, neighborY, grid);
    }
    return;
  }
  int numEnclaves(vector<vector<int>>& grid) {
    m = grid.size();
    if (m == 0)
      return count;
    n = grid[0].size();
    for (int i = 0; i < m; ++i) {
      dfs(i, 0, grid);
      dfs(i, n-1, grid);
    }
    for (int j = 0; j < n; ++j) {
      dfs(0, j, grid);
      dfs(m-1, j, grid);
    }
    for (int i = 0; i < m; ++i) {
      for (int j = 0; j < n; ++j) {
        if (grid[i][j] == 1) {
          ++count;
        }
      }
    }
    return count;
  }
};

695.岛屿的最大面积

给你一个大小为 m x n 的二进制矩阵 grid 。岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
岛屿的面积是岛上值为 1 的单元格的数目。计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0 。

示例 1:
输入:grid = [
[0,0,1,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,0,0,0],
[0,1,0,0,1,1,0,0,1,0,1,0,0],
[0,1,0,0,1,1,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,0,0,0,0,0,0,1,1,0,0,0,0]
]
输出:6 解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1 。

示例 2:输入:grid = [[0,0,0,0,0,0,0,0]] 输出:0

分析:
1.需要返回最大岛屿的面积, 因此遍历所有的位置, dfs需要返回从当前节点出发的面积

class Solution {
 private:
  int maxArea = 0;
  int m;
  int n;
 public:
  int dfs(int i, int j, vector<vector<int>>& grid) {
    if (i < 0 || j < 0 || i >= m || j >= n) {
      return 0;
    }
    if (grid[i][j] == 0) {
      return 0;
    }
    grid[i][j] = 0;
    return 1 + dfs(i+1, j, grid) + dfs(i, j+1, grid) + dfs(i-1, j, grid) + dfs(i, j-1, grid);
  }
  int maxAreaOfIsland(vector<vector<int>>& grid) {
    m = grid.size();
    if (m == 0)
      return 0;
    n = grid[0].size();
    for (int i = 0; i < m; ++i) {
      for (int j = 0; j < n; ++j) {
        if (grid[i][j] == 1) {
          maxArea = max(maxArea, dfs(i, j, grid));
        }
      }
    }
    return maxArea;
  }
};

1905.统计子岛屿

给你两个 m x n 的二进制矩阵 grid1 和 grid2 ,它们只包含 0 (表示水域)和 1 (表示陆地)。一个 岛屿 是由 四个方向 (水平或者竖直)上相邻的 1 组成的区域。任何矩阵以外的区域都视为水域。如果 grid2 的一个岛屿,被 grid1 的一个岛屿 完全 包含,也就是说 grid2 中该岛屿的每一个格子都被 grid1 中同一个岛屿完全包含,那么我们称 grid2 中的这个岛屿为 子岛屿 。请你返回 grid2 中 子岛屿 的 数目 。

示例 1:输出:3 解释:如上图所示,左边为 grid1 ,右边为 grid2 。grid2 中标红的 1 区域是子岛屿,总共有 3 个子岛屿。

示例 2:输入:grid1 = [
[1,0,1,0,1],
[1,1,1,1,1],
[0,0,0,0,0],
[1,1,1,1,1],
[1,0,1,0,1]
],
grid2 = [
[0,0,0,0,0],
[1,1,1,1,1],
[0,1,0,1,0],
[0,1,0,1,0],
[1,0,0,0,1]
]
输出:2 解释:如上图所示,左边为 grid1 ,右边为 grid2 。grid2 中标红的 1 区域是子岛屿,总共有 2 个子岛屿。

分析:
1.子岛屿的判断条件: grid2 中的岛屿完全存在 grid1 中某个岛屿中; 否明命题是grid2中的某一块陆地 grid[i][i] 在 grid1 中是海洋, 那么就不是子岛屿
2.这里一种判断方法是, 先排除所有的不可能是子岛屿的岛屿, 然后用水淹掉, 剩下的岛屿那么就是子岛屿了

class Solution {
 private:
  int m;
  int n;
  int cnt = 0;
 public:
  void dfs(int i, int j, vector<vector<int>>& grid2) {
    if (i < 0 || j < 0 || i >= m || j >= n)
      return;
    if (grid2[i][j] == 0)
      return;
    grid2[i][j] = 0;
    dfs(i+1, j, grid2);
    dfs(i-1, j, grid2);
    dfs(i, j+1, grid2);
    dfs(i, j-1, grid2);
    return;
  }
  int countSubIslands(vector<vector<int>>& grid1, vector<vector<int>>& grid2) {
    m = grid2.size();
    if (m == 0)
      return cnt;
    n = grid2[0].size();
    for (int i = 0; i < m; ++i) {
      for (int j = 0; j < n; ++j) {
        if (grid2[i][j] == 1 && grid1[i][j] == 0) {
          dfs(i, j, grid2);
        }
      }
    }
    for (int i = 0; i < m; ++i) {
      for (int j = 0; j < n; ++j) {
        if (grid2[i][j] == 1) {
          ++cnt;
          dfs(i, j, grid2);
        }
      }
    }
    return cnt;
  }
};

694.不同岛屿的数量

给定一个非空 01 二维数组表示的网格,一个岛屿由四连通(上、下、左、右四个方向)的 1 组成,你可以认为网格的四周被海水包围。
请你计算这个网格中共有多少个形状不同的岛屿。两个岛屿被认为是相同的,当且仅当一个岛屿可以通过平移变换(不可以旋转、翻转)和另一个岛屿重合。

示例 1:输入: grid = [
[1,1,0,0,0],
[1,1,0,0,0],
[0,0,0,1,1],
[0,0,0,1,1]
]
输出:1

示例 2:输入: grid = [
[1,1,0,1,1],
[1,0,0,0,0],
[0,0,0,0,1],
[1,1,0,1,1]
] 输出: 3

分析:
1.这道题需要找到形状不同的岛屿的数量, 相同仅仅定义为平移相同, 不包括翻转和旋转相同; 需要编码遍历的路径, 也就是说对遍历路径编码成唯一的字符串
2.首先对于相同形状的岛屿, 如果从统一起点出发, dfs 遍历的顺序是一样的

void dfs(int i, int j, vector<vector<int>>& grid2) {
  dfs(i-1, j, grid2); // 下
  dfs(i+1, j, grid2); // 上
  dfs(i, j-1, grid2); // 右
  dfs(i, j+1, grid2); // 右
  return;
}

3.对于以下的岛屿, 遍历顺序如果下, 右, 上, 撤销上(下), 撤销右(左), 撤销下(上)
11
11
得到的序列是唯一的2,4,1,-1,-4,-2

4.为啥非得用撤销这种操作呢? 比如我们遍历如下结果
1
11
可以用[下, 右]不行吗? 不行, 用撤销是为了区分两种可能的遍历顺序, 例如[下, 右, 撤销下, 撤销右]和[下, 撤销下, 右, 撤销右]

5.所以我们遍历的时候增加个方向, 用1,2,3,4分别表示上下左右, 用-1,-2,-3,-4表示撤销对应的上下左右, 就可以唯一化编码路径

class Solution {
 public:
  int m;
  int n;
  void dfs(int i, int j, string& s, int dir, vector<vector<int>>& grid) {
    if (i < 0 || j < 0 || i >= m || j >= n)
      return;
    if (grid[i][j] == 0)
      return;

    // 前序遍历位置
    grid[i][j] = 0;
    s += ",";
    dfs(i-1, j, s, 1, grid); // 下
    dfs(i+1, j, s, 2, grid); // 上
    dfs(i, j-1, s, 3, grid); // 右
    dfs(i, j+1, s, 4, grid); // 右

    // 后序遍历位置: 进行一个回溯操作
    s += to_string(-dir);
    s += ",";
    return;
  }
  int numDistinctIslands(vector<vector<int>>& grid) {
    m = grid.size();
    n = grid[0].size();
    unordered_set<string> hash;
    for (int i = 0; i < m; ++i) {
      for (int j = 0; j < n; ++j) {
        if (grid[i][j] == 1) {
          string s = "";
          dfs(i, j, s, 0, grid);
          hash.insert(s);
        }
      }
    }
    return hash.size();
  }
};

711.不同岛屿的数量 II

给定一个 m x n 二进制数组表示的网格 grid ,一个岛屿由 四连通 (上、下、左、右四个方向)的 1 组成(代表陆地)。你可以认为网格的四周被海水包围。
如果两个岛屿的形状相同,或者通过旋转(顺时针旋转 90°,180°,270°)、翻转(左右翻转、上下翻转)后形状相同,那么就认为这两个岛屿是相同的。
返回 这个网格中形状 不同 的岛屿的数量 。

示例 1: 输入: grid = [
[1,1,0,0,0],
[1,0,0,0,0],
[0,0,0,0,1],
[0,0,0,1,1]
] 输出: 1
解释: 这两个是相同的岛屿。因为我们通过 180° 旋转第一个岛屿,两个岛屿的形状相同。

示例 2: 输入: grid = [
[1,1,0,0,0],
[1,1,0,0,0],
[0,0,0,1,1],
[0,0,0,1,1]
] 输出: 1

分析:
1.这道题比上一道题复杂, 在于说如果是完全平移变化, 只用遍历的顺序可以实现序列化编码, 但是这道题带了旋转, 就需要进行唯一形状编码. 如何编码唯一化的图形? 有两种常见的做法, 一种只是利用旋转翻转平移的几何关系进行编码, 另一种是利用欧式距离取哈希编码
2.可以将岛屿的每块陆地坐标 (x,y) 都看做是向量, 然后利用解析几何只是计算出每个向量变换后的结果, 依次是 (以下旋转操作默认为顺时针旋转)
对坐标 (x,y)
顺时针旋转90度/180度/270度分别得到(y,-x) (-x,y) (-x,-y)
左右翻转(-x,y)
上下翻转(x,-y)
还有两种比较复杂的翻转
旋转90度且左右翻转(y,x) 等价于旋转270度且上下翻转
旋转270度且左右翻转(-y,-x) 等价于旋转90度且上下翻转
因此, 以上集中复杂的方式可以合并为8种可能性, 对于每一种旋转得到的结果, 需要先算出每个点的归一化的坐标值, 也就是是说以原点为(0, 0)的坐标值 归一化坐标值的方法是: 对于每一个坐标点, 先找到8个(剩余7个)旋转点, 然后分别计算出这8个点的横坐标最小值和纵坐标的最小值, 然后用8个坐标点都去减去这个最小值, 得到归一化坐标点之后 对每个坐标进行dfs, 并找到8个点进行规范化canocialize, 规范化的操作: 比如把8个生成的坐标点放在vector>> shapes里面
3.上面的方法实现还是比较复杂的, 这里hash: 对于岛屿上的所有点, 可以用两两坐标之间的欧式距离和进行哈希

分析:
1.按照解析几何关系编码的方法写的时间很长, 能用欧式距离编码就用欧式距离编码, 代码不容易出错
2.对任意岛屿坐标集合, hash = sum(两两坐标点欧式距离)

class Solution {
public:
  int m;
  int n;
  int count = 0;
  vector<double> distanceSumHash;
  vector<pair<int, int>> coordinates;
  const double eps = 1e-10;

  void dfs(int i, int j, vector<vector<int>>& grid) {
    if (i < 0 || j < 0 || i >= m || j >= n)
      return;
    if (grid[i][j] == 0)
      return;
    grid[i][j] = 0;
    coordinates.push_back({i, j});
    dfs(i+1, j, grid);
    dfs(i, j+1, grid);
    dfs(i-1, j, grid);
    dfs(i, j-1, grid);
    return;
  }

  double calcuSumDistanceHash() {
    double sum = 0;
    for (int i = 0; i < coordinates.size(); ++i) {
      for (int j = i+1; j < coordinates.size(); ++j) {
        double dist = 0;
        int x1 = coordinates[i].first;
        int y1 = coordinates[i].second;
        int x2 = coordinates[j].first;
        int y2 = coordinates[j].second;
        int dx = x1 - x2;
        int dy = y1 - y2;
        sum += sqrt(dx * dx + dy * dy);
      }
    }
    return sum;
  }

  bool checkConflict(double curHash) {
    bool ifConflict = false;
    for (double d : distanceSumHash) {
      if (abs(d - curHash) < eps) {
        ifConflict = true; 
        break;
      }
    }
    return ifConflict;
  }

  int numDistinctIslands2(vector<vector<int>>& grid) {
    m = grid.size();
    if (m == 0)
      return count;
    n = grid[0].size();
    for (int i = 0; i < m; ++i) {
      for (int j = 0; j < n; ++j) {
        if (grid[i][j] == 1) {
          coordinates.clear();
          dfs(i, j, grid);
          double hashValue = calcuSumDistanceHash();
          if (!checkConflict(hashValue)) {
            distanceSumHash.push_back(hashValue);
          }
        }
      }
    }
    return distanceSumHash.size();
  }
};

转载请注明来源, from goldandrabbit.github.io

💰

×

Help us with donation