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(); // rowint m = sc.nextInt(); // colint[][] 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];// 从第一组边界出发进行DFSfor (int i = 0; i < n; i++) {dfs(graph, canReachFirstGroup, i, 0);}for (int j = 0; j < m; j++) {dfs(graph, canReachFirstGroup, 0, j);}// 从第二组出发进行DFSfor (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(); // rowint m = sc.nextInt(); // colint[][] 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 之后的最大面积。