Skip to content

Latest commit

 

History

History
255 lines (238 loc) · 8.91 KB

0041.岛屿数量.md

File metadata and controls

255 lines (238 loc) · 8.91 KB

41. 岛屿数量

题目链接

C++

// 本题使用dfs
// 总体思路如下:
// 每次进行一个操作, 进行判断
// 如果该操作越界或要操作的地方已经是岛屿了
// 那么跳过此操作, ans不变并且输出ans
// 如果这个操作没问题, 我们将该岛屿编号为ans
// 就检查该操作的上下左右是否有岛屿
// 根据岛屿编号的不同, 我们每发现一个新的编号
// 就说明该编号的岛屿和新添加的岛屿连通了
// 那么岛屿数量 - 1
// 检查完上下左右以后, 我们得到新的ans
// 然后利用dfs把与当前我们操作的岛屿连通的所有岛屿都编号为ans
#include <bits/stdc++.h>
using namespace std;
 
int n, m;
int dir[4][2] = {1, 0, -1, 0, 0, 1, 0, -1}; // 表示方向
 
void dfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y, int num) {
    // 遍历四个方向
    for(int i = 0; i < 4; ++i) {
        int nx = x + dir[i][0];
        int ny = y + dir[i][1];
        // 跳过越界的情况和海洋
        if(nx < 0 || nx >= grid.size() || ny < 0 || ny >= grid[0].size() || visited[nx][ny] == true || grid[nx][ny] == 0) {
            continue;
        }
        // 标记为已经访问过
        visited[nx][ny] = true;
        // 将该岛屿编号
        grid[nx][ny] = num;
        dfs(grid, visited, nx, ny, num);
    }
} 
 
void solve() {
    vector<vector<int>> grid(n, vector<int>(m, 0)); // 地图
    int control; // 操作数
    cin >> control;
    int ans = 0;
    while(control--) {
        int x;
        int y;
        cin >> x >> y;
        // 如果该操作越界或要操作的地方已经是岛屿就直接输出答案
        if(x < 0 || x >= n || y < 0 || y >= m || grid[x][y] != 0) {
            cout << ans << " ";
            continue;
        }
        // 岛屿数量增加
        ans++;
        // 将新岛屿编号
        grid[x][y] = ans;
        unordered_set<int> st; // 用来储存不同的岛屿
        // 遍历四个方向
        for(int i = 0; i < 4; ++i) {
            int nx = x + dir[i][0];
            int ny = y + dir[i][1];
            // 跳过越界的情况和海洋
            if(nx < 0 || nx >= n || ny < 0 || ny >= m || grid[nx][ny] == 0) {
                continue;
            }
            // 如果出现新的岛屿
            // 说明这个岛屿与我们要操作的地方构成连通
            // 岛屿数量 - 1 并且将新岛屿加入set
            if(!st.count(grid[nx][ny])) {
                st.insert(grid[nx][ny]);
                ans--;
            }
        }
        vector<vector<bool>> visited(n, vector<bool>(m, false)); // 储存访问情况
        // 标记为已经访问
        visited[x][y] = true;
        // 将编号改为我们新得到的ans
        grid[x][y] = ans;
        // 将所有与当前操作的岛屿连通的岛屿都编号为我们新得到的ans
        dfs(grid, visited, x, y, ans);
        // 输出答案
        cout << ans << " ";
    }
    cout << endl;
}
 
int main()
{
    while(cin >> n >> m) {
        solve();
    }
    return 0;
}

Java

/**
 * 思路:可以使用并查集来解决该问题。每次 addLand 时,可以将新加入的陆地与其相邻的陆地进行合并,
 * 然后计算岛屿的数量。
 * 
 * 首先创建一维数组,表示每个单元格的父节点,初始每个单元格的父节点都设置为自己。
 * 再初始化一个变量用于记录岛屿的数量
 * 
 * 对于每次 addLand 操作,首先将父节点设置为自己,然后检查周围的上下左右四个单元格,
 * 如果相邻的单元格也是陆地,则将当前的单元格与相邻的单元格进行合并(将当前单元格的父节点设置为另外一个单元格)
 * 同时岛屿的数量 -1
 * 
 * 每次 addLand 操作后,将当前的岛屿数量存入结果列表。
*/
import java.util.*;

public class Main {
  // 4个方向
  final static int[][] dir = {{1, -1, 0, 0}, {0, 0, 1, -1}};
  /**
   * 在并查集中,我们需要为每个单元格或节点分配一个唯一的标识符,以便能够在操作中识别它们。
   * 二维地图中的每个单元格可以由其行号和列号唯一确定。然而,并查集的数据结构是基于数组实现的,而数组是一维的。
   * 因此,我们需要将二维的坐标映射到一维的索引,以便在数组中表示这些单元格。
   * 考虑一个 m 行 n 列的二维地图,可以用 (row, col) 表示其中一个单元格的坐标。
   * 通过映射这个坐标到一维数组中的索引,我们可以使用一个一维数组来表示这个二维地图,并进行并查集的操作。
   * 映射的方式很多,只需要保证每个单元格都有一个唯一的一维索引,而且不会与其他单元格冲突。
   **/
  static int hashCor(int r, int c, int col) {
    return r * col + c + 1;// 加一是因为要将 0 当作水,加上 1 避免和 0 冲突
  }
  // 如果par[x] 为 0,则代表该点是水
  static int[] par, rank;
  static int find(int x) {
    return x == par[x] ? x : (par[x] = find(par[x]));
  }
  static void union(int x, int y) {
    if ((x = find(x)) == (y = find(y)))
      return;
    if (rank[x] < rank[y]) {
      par[x] = y;
    } else {
      par[y] = x;
      if (rank[x] == rank[y])
        rank[x]++;
    }
  }
  static boolean same(int x, int y) {
    return find(x) == find(y);
  }
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    int isNum;    // 当前存在的岛屿个数。
    int row, col;  // 地图的行数与列数
    int i;
    int r, c;  // 当前正在处理的单元格的坐标
    int opNum;  // addLand 的总次数
    int nearR, nearC; // 相邻单元格的坐标
    while (sc.hasNext()) {
      row = sc.nextInt();
      col = sc.nextInt();
      opNum = sc.nextInt();
      isNum = 0;
      par = new int[row * col + 1]; // 一开始每个点的父节点均为0,代表水
      rank = new int[row * col + 1];
      while (opNum-- > 0) {
        r = sc.nextInt();
        c = sc.nextInt();
        // 如果行号r和列号c不越界且该点当前是水
        if (r >= 0 && r < row && c >= 0 && c < col && find(hashCor(r,c,col)) == 0){
          isNum++;
          par[hashCor(r, c, col)] = hashCor(r, c, col);
          for (i = 0; i < 4; ++i) {//遍历临近 4 个点的坐标
            nearR = r + dir[0][i];
            nearC = c + dir[1][i];
            // 如果该点不越界且是一块陆地且和当前的这一点不属于同一个岛
            if (nearR >= 0 && nearR < row && nearC >= 0 && nearC < col &&
                find(hashCor(nearR, nearC, col)) != 0 &&
                !same(hashCor(nearR, nearC, col), hashCor(r, c, col))) {
              // 那么把它们连成同一个岛
              union(hashCor(nearR, nearC, col), hashCor(r, c, col));
              isNum--;// 因为把两块不同的岛连成同一个岛了,所以岛的数目减一
            }
          }
        }
        System.out.printf("%d%c", isNum, opNum==0 ? '\n' : ' ');
      }
    }
  }
}

Python

class Island:
    def __init__(self, m, n):
        self.island = [[(-1, -1) for _ in range(n)] for _ in range(m)]
        self.m = m
        self.n = n
        self.size = {}
        self.count = 0  # 初始时有 0 个岛屿

    def find(self, x, y):
        if self.island[x][y] == (x, y) or self.island[x][y] == (-1, -1):
            return self.island[x][y]
        x_p, y_p = self.island[x][y]
        self.island[x][y] = self.find(x_p, y_p)  # 路径压缩
        return self.island[x][y]

    def union(self, i1, i2):
        x1, y1 = i1
        x2, y2 = i2
        root1x, root1y = self.find(x1, y1)
        root2x, root2y = self.find(x2, y2)
        if (root1x, root1y) != (root2x, root2y):
            if self.size[(root1x, root1y)] < self.size[(root2x, root2y)]:
                root1x, root1y, root2x, root2y = root2x, root2y, root1x, root1y
            self.island[root2x][root2y] = (root1x, root1y)
            self.size[(root1x, root1y)] += self.size[(root2x, root2y)]
            self.count -= 1  # 合并后集合数量减少

    def add_element(self, x, y):
        if self.find(x, y) == (-1, -1):
            self.count += 1
            self.size[x, y] = 1
            self.island[x][y] = (x, y)
            dx = [-1, 0, 1, 0]
            dy = [0, 1, 0, -1]
            for i in range(4):
                new_x = x + dx[i]
                new_y = y + dy[i]
                if 0 <= new_x < self.m and 0 <= new_y < self.n:
                    if self.find(new_x, new_y) != (-1, -1):
                        self.union(i1=(x, y), i2=(new_x, new_y))
            
    def get_count(self):
        return self.count

m = int(input())
n = int(input())
island = Island(m=m, n=n)
k = int(input())
res = []
for i in range(k):
    x, y = map(int, input().split(" "))
    if 0 <= x < m and 0 <= y < n:
        island.add_element(x=x, y=y)
    res.append(island.get_count())
res = ' '.join(map(str, res))
print(res)

Go

JS

C