[LeetCode] 463. Island Perimeter 解题报告

You are given a map in form of a two-dimensional integer grid where 1 represents land and 0 represents water. Grid cells are connected horizontally/vertically (not diagonally). The grid is completely surrounded by water, and there is exactly one island (i.e., one or more connected land cells). The island doesn't have "lakes" (water inside that isn't connected to the water around the island). One cell is a square with side length 1. The grid is rectangular, width and height don't exceed 100. Determine the perimeter of the island.

Example:

[[0,1,0,0],
 [1,1,1,0],
 [0,1,0,0],
 [1,1,0,0]]

Answer: 16
Explanation: The perimeter is the 16 yellow stripes in the image below:

这一题是easy难度的,比较简单,提供三种解法:

方法一:

树搜索法,DFS/BFS。

这种方法是比较通用的,简单原理就是首先找到一块小岛(利用循环找小岛就可以了)。使用循环和stack/queue为空进行判断,每搜索一块小岛时,判断周围四个方向是否为小岛,如果四个方向都是小岛,那么当前方块的周长就是0;如果三个方向有,那么就是1,以此类推,将每个周长累加。然后搜索完所有的相邻小岛,直到数据结构中的内容为空,循环结束,返回累加值即可。

这个方法对于不是相邻的小岛也可以的,最开始的循环不要结束,一直找下去就可以了。

代码如下,复杂度大致是O(N^2),平均复杂度估计在N^2/2和N^2之间,接近N^2,并且如果运气不好很容易超过N^2,一直找不到小岛就比较糟糕了:

public class Solution {
	int[][] grid;
	boolean[][] isExpl;
	Queue queue;

	public int islandPerimeter(int[][] grid) {
		this.grid = grid;
		isExpl = new boolean[grid.length][grid[0].length];
		int nPerimeter = 0;
		queue = new LinkedList<>();
		// init queue, find the first grid of island
		for (int i = 0; i < grid.length; i++) {
			for (int j = 0; j < grid[0].length; j++) {
				isExpl[i][j] = true;
				if (grid[i][j] == 1) {
					queue.add(new int[] { i, j });
					break;
				}
			}
		}
		// start bfs
		while (queue.size() > 0) {
			int[] nnCur = queue.poll();
			int x = nnCur[0];
			int y = nnCur[1];
			int nSurrWater = 4;
			if (!isWater(x + 1, y)) {
				addPoint(x + 1, y);
				nSurrWater--;
			}
			if (!isWater(x - 1, y)) {
				addPoint(x - 1, y);
				nSurrWater--;
			}
			if (!isWater(x, y + 1)) {
				addPoint(x, y + 1);
				nSurrWater--;
			}
			if (!isWater(x, y - 1)) {
				addPoint(x, y - 1);
				nSurrWater--;
			}
			nPerimeter += nSurrWater;
		}

		return nPerimeter;

	}

	private boolean isWater(int x, int y) {
		if (x < 0 || y < 0 || x >= grid.length || y >= grid[0].length || grid[x][y] == 0) {
			return true;
		} else {
			return false;
		}
	}

	private void addPoint(int x, int y) {
		if (!isExpl[x][y]) {
			queue.add(new int[] { x, y });
			isExpl[x][y] = true;
		}
	}
}

方法二:

数格子算边长法。

这个方法非常简单,需要遍历两次,横着算一次,竖着算一次周长。具体来说,看到上图的第一列,从上往下遍历,首先是海,然后遇到大陆,发现和上一个不一样,于是边长++;继续,发现大海,和上一个不一样,++;继续,发现大陆,和上一个不一样,++;遇到边界,把边界认为是大海,又和上一个不一样,++。所以第一列,边长为4。

这个方法的原理就是,相邻两个格子,如果不一样的话,才会形成边界,需要单位边长,否则就不会形成边界。

代码如下,复杂度是稳定的N^2:

  

public class Solution {
	public int islandPerimeter(int[][] grid) {
		int nPerimeter = 0;
		for (int i = 0; i < grid.length; i++) {
			int nLastOne = 0;
			for (int j = 0; j < grid[0].length; j++) {
				if (grid[i][j] != nLastOne) {
					nPerimeter++;
					nLastOne = grid[i][j];
				}
			}
			if (nLastOne != 0) {
				nPerimeter++;
			}
		}
		for (int i = 0; i < grid[0].length; i++) {
			int nLastOne = 0;
			for (int j = 0; j < grid.length; j++) {
				if (grid[j][i] != nLastOne) {
					nPerimeter++;
					nLastOne = grid[j][i];
				}
			}
			if (nLastOne != 0) {
				nPerimeter++;
			}
		}
		return nPerimeter;
	}
}

方法三:

最简单,和方法二的原理类似,但是更不容易想到,也是计算法,这种方法复杂度更低,只需要遍历一次。需要计算所有岛屿格子的数量,遍历的时候,如果当前格子是岛屿,还要计算右边和下面的是岛屿格子数量,最后结果等于4*island-2*neighbor。解释来说,4*island就是岛屿所有格子的边长,每个相邻的边,都算了两次,实际上这个相邻的边是不计入周长的,所以减去2*neighbor。


public class Solution {
    public int islandPerimeter(int[][] grid) {
        int islands = 0, neighbours = 0;

        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[i].length; j++) {
                if (grid[i][j] == 1) {
                    islands++; // count islands
                    if (i < grid.length - 1 && grid[i + 1][j] == 1) neighbours++; // count down neighbours
                    if (j < grid[i].length - 1 && grid[i][j + 1] == 1) neighbours++; // count right neighbours
                }
            }
        }

        return islands * 4 - neighbours * 2;
    }
}


你可能感兴趣的:(LeetCode)