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

101.孤岛的总面积 

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

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

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 (陆地)

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来举例。

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);
            }
        }
    }


}

优化

#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。

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 之后的最大面积。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值