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