题目描述
标题:全球变暖
你有一张某海域NxN像素的照片,".“表示海洋、”#"表示陆地,如下所示:
…
.##…
.##…
…##.
…####.
…###.
…
其中"上下左右"四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有2座岛屿。
由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。
例如上图中的海域未来会变成如下样子:
…
…
…
…
…#…
…
…
请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。
【输入格式】
第一行包含一个整数N。 (1 <= N <= 1000)
以下N行N列代表一张海域照片。
照片保证第1行、第1列、第N行、第N列的像素都是海洋。
【输出格式】
一个整数表示答案。
【输入样例】
7
…
.##…
.##…
…##.
…####.
…###.
…
【输出样例】
1
资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 1000ms
请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。
所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
不要使用package语句。不要使用jdk1.7及以上版本的特性。
主类的名字必须是:Main,否则按无效代码处理。
java代码BFS版本
对于淹没之前的陆地,利用BFS计算有几个岛屿,模拟淹没以后再用BFS计算有几个岛屿,做差即可。
import java.util.Arrays;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Scanner;
public class Main {
static int N;
static boolean[][] data; // 标记数据是海洋还是陆地
static boolean[][] visited; // 标记数据是否已经遍历
static boolean[][] marked; // 标记某点未来是否会被海洋覆盖
static int before;
static int after;
static Queue<Integer> queue;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
data = new boolean[N][N];
visited = new boolean[N][N];
marked = new boolean[N][N];
for (int i = 0; i < N; i++) {
String s = sc.next();
for (int j = 0; j < N; j++) {
if (s.charAt(j) == '#') {
data[i][j] = true;
}
}
}
// 计算之前有多少岛屿,注意这里的边界
for (int i = 1; i < N - 1; i++) {
for (int j = 1; j < N - 1; j++) {
// 该点是陆地并且未被标记,则以该点为起点bfs
if (data[i][j] && !visited[i][j]) {
bfs(i, j);
before++;
}
}
}
// 模拟岛屿被海洋覆盖
for (int i = 0; i < N; i++) {
for (int j = 1; j < N; j++) {
if (marked[i][j]) {
data[i][j] = false;
}
}
}
// 恢复所有的顶点为未访问过,为接下来的遍历做准备
for (int i = 0; i < N; i++) {
Arrays.fill(visited[i], false);
}
// 计算覆盖后有多少岛屿
for (int i = 1; i < N - 1; i++) {
for (int j = 1; j < N - 1; j++) {
// 该点是陆地并且为被标记,则以该点为起点遍历陆地
if (data[i][j] && !visited[i][j]) {
bfs(i, j);
after++;
}
}
}
// 输出
System.out.println(before - after);
sc.close();
}
// 以data[i][j]为起点遍历连通图
// 同时进行add操作
private static void bfs(int i, int j) {
queue = new PriorityQueue<Integer>();
queue.add(Integer.valueOf(i * N + j));
visited[i][j] = true;
Integer integer;
while (true) {
integer = queue.poll();
if (integer == null) {
break;
}
i = integer.intValue() / N;
j = integer.intValue() % N;
marks(i, j);
add(i - 1, j);
add(i + 1, j);
add(i, j - 1);
add(i, j + 1);
}
}
// 如果该点未来会被海洋覆盖,则标记该点将被覆盖
private static void marks(int i, int j) {
if (i - 1 >= 0 && !data[i - 1][j]) {
marked[i][j] = true;
}
if (i + 1 < N && !data[i + 1][j]) {
marked[i][j] = true;
}
if (j - 1 >= 0 && !data[i][j - 1]) {
marked[i][j] = true;
}
if (j + 1 < N && !data[i][j + 1]) {
marked[i][j] = true;
}
}
// 如果该点未被遍历,则标记该点已经遍历,同时将该点加入queue队列中
private static void add(int i, int j) {
if (data[i][j]) {
if (!visited[i][j]) {
visited[i][j] = true;
queue.add(i * N + j);
}
}
}
}
java代码BFS版本优化
之前自己写的代码简直是又臭又长,且看如下代码:
每次bfs的时候就标注该岛屿所有点的数量以及未来会被淹没点的数量,若两者相等,说明该岛屿一定被淹没,ans++
package test;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
public class Main {
static int N;
static char[][] data; // 标记数据是海洋还是陆地
static boolean[][] visited; // 标记数据是否已经遍历
static int ans = 0;
static int[][] delta = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } }; // 标记上下左右四个方向
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
data = new char[N][]; // 注意这里第二维可以不给出具体数字,但是一定要给出括号
visited = new boolean[N][N];
for (int i = 0; i < N; i++) {
data[i] = sc.next().toCharArray();
}
// 注意这里的下标
for (int i = 1; i < N - 1; i++) {
for (int j = 1; j < N - 1; j++) {
// 该点是陆地并且未被标记,则以该点为起点bfs
if (data[i][j] == '#' && !visited[i][j]) {
bfs(i, j);
}
}
}
System.out.println(ans);
sc.close();
}
// 遍历图
private static void bfs(int i, int j) {
int cnt1 = 0, cnt2 = 0; // cnt1表示以i,j为起点的岛屿有多少个#,cnt2表示以i,j为起点的岛屿将有多少个点会被海洋覆盖
// 加入起点
Queue<Point> queue = new LinkedList<Point>();
queue.add(new Point(i, j));
visited[i][j] = true;
// 循环处理队列直至为空
while (true) {
Point p = queue.poll();
if (p == null) {
break;
}
cnt1++;// #数量加一
int x = p.x;
int y = p.y;
boolean flag = false; // 标记当前点是否会被覆盖
for (int k = 0; k < 4; k++) {
int xx = x + delta[k][0];
int yy = y + delta[k][1];
// 如果该点没有越界
if (xx >= 0 && yy >= 0 && xx < N && yy < N) {
// 如果当前点是陆地并且未被访问过
if (data[xx][yy] == '#' && !visited[xx][yy]) {
visited[xx][yy] = true;
queue.add(new Point(xx, yy));
}
// 如果当前点是海洋,那么一定会造成p被覆盖
if (data[xx][yy] == '.') {
flag = true;
}
}
}
// 如果p会被海洋覆盖
if (flag) {
cnt2++;
}
}
if (cnt1 == cnt2) {
ans++;
}
}
private static class Point {
int x;
int y;
public Point(int i, int j) {
x = i;
y = j;
}
}
}
java代码DFS版本
只是为了熟悉一下DFS,与上述BFS思路差不多
package test;
import java.util.Scanner;
public class Main {
static int cnt1 = 0, cnt2 = 0; // cnt1表示以i,j为起点的岛屿有多少个#,cnt2表示以i,j为起点的岛屿将有多少个点会被海洋覆盖
static int N;
static char[][] data; // 标记数据是海洋还是陆地
static boolean[][] visited; // 标记数据是否已经遍历
static int ans = 0;
static int[][] delta = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } }; // 标记上下左右四个方向
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
data = new char[N][]; // 注意这里第二维可以不给出具体数字,但是一定要给出括号
visited = new boolean[N][N];
for (int i = 0; i < N; i++) {
data[i] = sc.next().toCharArray();
}
// 注意这里的下标
for (int i = 1; i < N - 1; i++) {
for (int j = 1; j < N - 1; j++) {
// 该点是陆地并且未被标记,则以该点为起点bfs
if (data[i][j] == '#' && !visited[i][j]) {
// 初始化cnt
cnt1 = cnt2 = 0;
dfs(i, j);
// 如果#数量等于该岛屿上将被覆盖的点
if (cnt1 == cnt2) {
ans++;
}
}
}
}
System.out.println(ans);
sc.close();
}
// 遍历图
private static void dfs(int i, int j) {
// 加入起点
visited[i][j] = true;
cnt1++;// #数量加一
boolean flag = false;
for (int k = 0; k < 4; k++) {
int xx = i + delta[k][0];
int yy = j + delta[k][1];
// 如果该点没有越界
if (xx >= 0 && yy >= 0 && xx < N && yy < N) {
// 如果当前点是陆地并且未被访问过
if (data[xx][yy] == '#' && !visited[xx][yy]) {
visited[xx][yy] = true;
dfs(xx, yy); // 深搜下去
} // 如果当前点是海洋,那么一定会造成p被覆盖
if (data[xx][yy] == '.') {
flag = true;
}
}
}
// 如果该点会被海洋覆盖
if (flag) {
cnt2++;
}
}
private static class Point {
int x;
int y;
public Point(int i, int j) {
x = i;
y = j;
}
}
}