day 66 图论part03 101.孤岛的总面积 102.沉没孤岛 103.水流问题 104.建造最大岛屿

101.孤岛的总面积

本题使用dfs,bfs,并查集都是可以的。

本题要求找到不靠边的陆地面积,那么我们只要从周边找到陆地然后 通过 dfs或者bfs 将周边靠陆地且相邻的陆地都变成海洋,然后再去重新遍历地图 统计此时还剩下的陆地就可以了。

java 复制代码
import java.util.*;
import java.lang.Math.*;

public class Main{
    public static void main(String[] args){
        Scanner scanner = new Scanner(System.in);
        int N = scanner.nextInt();
        int M = scanner.nextInt();
        int[][] grid = new int[N][M];
        for(int i = 0; i < N; i++){
            for(int j = 0; j < M; j++){
                grid[i][j] = scanner.nextInt();
            }
        }
        Solution solution = new Solution();
        System.out.println(solution.getSingle(grid, N, M));
        return;
    }
    
}
class Solution{
    int[][] directions;
    
    Solution(){
        directions = new int[][]{{-1, 0}, {1, 0}, {0, 1}, {0, -1}};
    }
    
    int getSingle(int[][] grid, int N, int M){
        boolean[][] visited = new boolean[N][M];
        for(boolean[] v : visited){
            Arrays.fill(v, false);
        }
        int ans = -1;
        for(int i = 0; i < N; i++){
            for(int j = 0; j < M; j++){
                if(grid[i][j] == 1 && !visited[i][j]){
                    ans = Math.max(ans, dfs(grid, visited, N, M, i, j));
                }
            }
        }
        return ans;
    }
    
    private int dfs(int[][] grid, boolean[][] visited, int N, int M, int i, int j){
        if(i < 0 || i >= N || j < 0 || j >= M || visited[i][j]){
            return 0;
        }
        visited[i][j] = true;
        if(grid[i][j] == 0){
            return 0;
        }
        if(grid[i][j] == 1 && (i == 0 || i == N - 1 || j == 0 || j == M - 1)){
            return -1;
        }
        int ret = 1;
        for(int k = 0; k < 4; k++){
            int rett = dfs(grid, visited, N, M, i + directions[k][0], j + directions[k][1]);
            if(rett == -1){
                ret = -1;
            }
            if(ret != -1){
                ret += rett;
            }
        }
        return ret;
    }
}

102.沉没孤岛

这道题目和0101.孤岛的总面积 (opens new window)正好反过来了,101.孤岛的总面积 (opens new window)是求 地图中间的空格数,而本题是要把地图中间的 1 都改成 0 。

思路依然是从地图周边出发,将周边空格相邻的陆地都做上标记,然后在遍历一遍地图,遇到 陆地 且没做过标记的,那么都是地图中间的 陆地 ,全部改成水域就行。

步骤一:深搜或者广搜将地图周边的 1 (陆地)全部改成 2 (特殊标记)

步骤二:将水域中间 1 (陆地)全部改成 水域(0)

步骤三:将之前标记的 2 改为 1 (陆地)

java 复制代码
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int m = scanner.nextInt();
        int n = scanner.nextInt();
        int[][] island = new int[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                island[i][j] = scanner.nextInt();
            }
        }
        handle(island);
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                System.out.print(island[i][j] + " ");
            }
            System.out.println("");
        }

    }

    private static final int[][] dirs = new int[][]{{0, 1}, {0, -1}, {1, 0}, {-1, 0}};

    private static void handle(int[][] island) {
        int m = island.length;
        int n = island[0].length;
        int x, y;
        x = 0;
        for (int j = 0; j < n; j++) {
            y = j;
            if (1 == island[x][y]) {
                dfs(island, x, y);
            }
        }
        x = m - 1;
        for (int j = 0; j < n; j++) {
            y = j;
            if (1 == island[x][y]) {
                dfs(island, x, y);
            }
        }
        y = 0;
        for (int i = 0; i < m; i++) {
            x = i;
            if (1 == island[x][y]) {
                dfs(island, x, y);
            }
        }
        y = n - 1;
        for (int i = 0; i < m; i++) {
            x = i;
            if (1 == island[x][y]) {
                dfs(island, x, y);
            }
        }

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (2 == island[i][j]) {
                    island[i][j] = 1;
                } else if (1 == island[i][j]) {
                    island[i][j] = 0;
                }
            }
        }

    }

    private static void dfs(int[][] island, int x, int y) {
        island[x][y] = 2;
        for (int[] dir : dirs) {
            int nx = x + dir[0];
            int ny = y + dir[1];
            if (nx < 0 || nx >= island.length || ny < 0 || ny >= island[0].length) {
                continue;
            }
            if (1 == island[nx][ny]) {
                dfs(island, nx, ny);
            }
        }
    }

}

103.水流问题

一个比较直白的想法,其实就是 遍历每个点,然后看这个点 能不能同时到达第一组边界和第二组边界。

至于遍历方式,可以用dfs,也可以用bfs,以下用dfs来举例。

java 复制代码
import com.sun.org.apache.xpath.internal.operations.Neg;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main {
    static int[][] dirs = {{1,0},{-1,0},{0,1},{0,-1}};
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt(); // row
        int m = sc.nextInt(); // col
        int[][] graph = new int[n][m];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                graph[i][j] = sc.nextInt();
            }
        }

        boolean[][] canReachFirstGroup = new boolean[n][m];
        boolean[][] canReachSecondGroup = new boolean[n][m];

        // 从第一组边界出发进行DFS
        for (int i = 0; i < n; i++) {
            dfs(graph, canReachFirstGroup, i, 0);
        }

        for (int j = 0; j < m; j++) {
            dfs(graph, canReachFirstGroup, 0, j);
        }

        // 从第二组出发进行DFS
        for (int i = 0; i < n; i++) {
            dfs(graph, canReachSecondGroup, i, m - 1);
        }

        for (int j = 0; j < m; j++) {
            dfs(graph, canReachSecondGroup, n - 1, j);
        }

        // 找出同时可以到达两组边界的单元格
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (canReachFirstGroup[i][j] && canReachSecondGroup[i][j]){
                    System.out.printf("%d %d\n",i ,j);
                }
            }
        }
    }

    private static void dfs(int[][] graph, boolean[][] canReach, int i, int j) {
        int n = graph.length;
        int m = graph[0].length;
        canReach[i][j] = true;

        for (int[] dir : dirs) {
            int newX = dir[0] + i;
            int newY = dir[1] + j;

            if (newX >= 0 && newX < n && newY >= 0 && newY < m && !canReach[newX][newY] && graph[newX][newY] >= graph[i][j]) {
                dfs(graph, canReach, newX, newY);
            }
        }
    }


}

优化

java 复制代码
#include <iostream>
#include <vector>
using namespace std;
int n, m;
int dir[4][2] = {-1, 0, 0, -1, 1, 0, 0, 1};
void dfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y) {
    if (visited[x][y]) return;

    visited[x][y] = true;

    for (int i = 0; i < 4; i++) {
        int nextx = x + dir[i][0];
        int nexty = y + dir[i][1];
        if (nextx < 0 || nextx >= n || nexty < 0 || nexty >= m) continue;
        if (grid[x][y] > grid[nextx][nexty]) continue; // 注意:这里是从低向高遍历

        dfs (grid, visited, nextx, nexty);
    }
    return;
}



int main() {

    cin >> n >> m;
    vector<vector<int>> grid(n, vector<int>(m, 0));

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> grid[i][j];
        }
    }
    // 标记从第一组边界上的节点出发,可以遍历的节点
    vector<vector<bool>> firstBorder(n, vector<bool>(m, false));

    // 标记从第一组边界上的节点出发,可以遍历的节点
    vector<vector<bool>> secondBorder(n, vector<bool>(m, false));

    // 从最上和最下行的节点出发,向高处遍历
    for (int i = 0; i < n; i++) {
        dfs (grid, firstBorder, i, 0); // 遍历最左列,接触第一组边界
        dfs (grid, secondBorder, i, m - 1); // 遍历最右列,接触第二组边界
    }

    // 从最左和最右列的节点出发,向高处遍历
    for (int j = 0; j < m; j++) {
        dfs (grid, firstBorder, 0, j); // 遍历最上行,接触第一组边界
        dfs (grid, secondBorder, n - 1, j); // 遍历最下行,接触第二组边界
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            // 如果这个节点,从第一组边界和第二组边界出发都遍历过,就是结果
            if (firstBorder[i][j] && secondBorder[i][j]) cout << i << " " << j << endl;;
        }
    }


}

104.建造最大岛屿

本题的一个暴力想法,应该是遍历地图尝试 将每一个 0 改成1,然后去搜索地图中的最大的岛屿面积。

计算地图的最大面积:遍历地图 + 深搜岛屿,时间复杂度为 n * n。

(其实使用深搜还是广搜都是可以的,其目的就是遍历岛屿做一个标记,相当于染色,那么使用哪个遍历方式都行,以下我用深搜来讲解)

每改变一个0的方格,都需要重新计算一个地图的最大面积,所以 整体时间复杂度为:n^4。

java 复制代码
import java.util.Arrays;
import java.util.Map;
import java.util.Scanner;

// 逆向思维
public class Main {
    static int[][] dirs = {{1,0},{-1,0},{0,1},{0,-1}};
    static int ans = 0, area = 1;
    static boolean[][] visited;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt(); // row
        int m = sc.nextInt(); // col
        int[][] graph = new int[n][m];

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                graph[i][j] = sc.nextInt();
            }
        }


        boolean flag  = false;

        // 遍历
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                area = 1;
                visited = new boolean[n][m];
                if (graph[i][j] == 0){
                    dfs(i, j, graph, n, m);
                    flag = true;

                }
            }
        }

        System.out.println(flag ? ans : n * m);
    }

    private static void dfs(int row, int col, int[][] graph, int n, int m) {
        ans = Math.max(ans, area);
        visited[row][col] = true;
        for (int[] dir : dirs) {
            int newX = dir[0] + row;
            int newY = dir[1] + col;
            if(newX >= 0 && newX < n && newY >= 0 && newY < m && graph[newX][newY] == 1 && !visited[newX][newY]){
                area++;
                dfs(newX,newY,graph,n,m);
            }
        }
    }

}

优化思路

其实每次深搜遍历计算最大岛屿面积,我们都做了很多重复的工作。

只要用一次深搜把每个岛屿的面积记录下来就好。

第一步:一次遍历地图,得出各个岛屿的面积,并做编号记录。可以使用map记录,key为岛屿编号,value为岛屿面积

第二步:再遍历地图,遍历0的方格(因为要将0变成1),并统计该1(由0变成的1)周边岛屿面积,将其相邻面积相加在一起,遍历所有 0 之后,就可以得出 选一个0变成1 之后的最大面积。

相关推荐
智者知已应修善业27 分钟前
【51单片机8位数码管同时倒计时从9999】2024-1-25
c++·经验分享·笔记·算法·51单片机
洛水水30 分钟前
【力扣100题】86.柱状图中最大的矩形
算法·leetcode·职场和发展
渡之37 分钟前
GRiM-Net 深度解析 | 无人机 GNSS 拒止场景下两阶段跨视角视觉定位框架
深度学习·算法·动态规划·无人机
测试仪器廖生135902563851 小时前
罗德与施瓦茨 FSP13频谱分析仪FSP30
网络·人工智能·算法
happymaker06261 小时前
LeetCodeHot100——560.和为K的子数组
算法
dtq04241 小时前
C语言刷题数组5,6(求平均值,求最大值)
c语言·数据结构·算法
郭梧悠1 小时前
Hash算法入门Hash冲突解决方案
算法·哈希算法
洛水水2 小时前
【力扣100题】81.寻找两个正序数组的中位数
数据结构·算法·leetcode
happymaker06263 小时前
LeetCodeHot100——155.最小栈
算法
洛水水3 小时前
【力扣100题】85.每日温度
算法·leetcode·职场和发展