看完题目我还感觉这道题目有点难,没想到20分钟不到就完全靠自己给写出来了。我就是按照自己的想法来,我用一个等大的visit数组来表示grid数组中的这个元素是否被访问过(是否已经被判断了是不是岛屿)。
先用一个大的循环对grid数组遍历,去判断里面的元素grid[i][j]是不是一个岛屿。如果它是一个岛屿的话,就要把岛屿数量+1,并且要把和grid[i][j]相连的陆地全部算在这个岛屿中,所以要把和它相连的陆地的visit设置成true,下次遍历到这个点就直接跳过。
那么如何把grid[i][j]的相连的陆地的visit全部置为true呢,利用递归就可以了,用setVisit(char[][] grid, boolean[][] visit, int i, int j),grid数组和visit数组就是全局的grid数组和visit数组(如果把这两个数组定义在类里面就不用传这两个参了),i,j就是要进行置true的元素下标,先把visit[i][j]置为true,然后再递归调用setVist()方法把visit[i][j]的上下左右4个方向的visit置为true,当然不是直接置true,要进行判断,假设相邻位置是m,n,首先必须要m,n大于等于0小于length,其次grid[m][n]要等于1并且visit[m][n]要等于false,然后直接递归调用setVisit方法把visit[m][n]置为true,这样进行递归就会把与grid[i][j]相连的所有陆地都visit了,
那么如何判断grid[i][j]是不是岛屿呢?其实很简单,如果grid[i][j]是1并且它没有被visit过他就是岛屿,因为它如果没有被visit过只有两种可能,第一种是没有任何陆地和它相连所以它没有被visit,第二种是它是它所在的岛屿第一个被发现的陆地,以上两种情况都可以把它判定为岛屿给岛屿属灵加一,最后返回岛屿数量result即可,以下是我的代码:
class Solution {
public int numIslands(char[][] grid) {
int m = grid.length;
int n = grid[0].length;
boolean[][] visit = new boolean [m][n];
int result=0;
for(int i =0;i<m;i++){
for(int j=0;j<n;j++){
if(grid[i][j] == '1' && !visit[i][j]){
result++;
setVisit(grid, visit, i, j);
}
}
}
return result;
}
public void setVisit(char[][] grid, boolean[][] visit, int i, int j){
visit[i][j] =true;
if(i+1<visit.length && grid[i+1][j] == '1' && visit[i+1][j] == false)setVisit(grid, visit, i+1, j);
if(j+1<visit[0].length && grid[i][j+1] == '1' && visit[i][j+1] == false)setVisit(grid, visit, i, j+1);
if(i-1>=0 && grid[i-1][j] == '1' && visit[i-1][j] == false)setVisit(grid, visit, i-1, j);
if(j-1>=0 && grid[i][j-1] == '1' && visit[i][j-1] == false)setVisit(grid, visit, i, j-1);
}
}
看看官方题解的做法吧:
题解的方法一用的是深度优先搜索,和我的方法是一样的,只不过它没有用标记数组visit,而是直接再grid数组上把相连的陆地由1改成了0,以下是题解方法一的代码:
class Solution {
void dfs(char[][] grid, int r, int c) {
int nr = grid.length;
int nc = grid[0].length;
if (r < 0 || c < 0 || r >= nr || c >= nc || grid[r][c] == '0') {
return;
}
grid[r][c] = '0';
dfs(grid, r - 1, c);
dfs(grid, r + 1, c);
dfs(grid, r, c - 1);
dfs(grid, r, c + 1);
}
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
int nr = grid.length;
int nc = grid[0].length;
int num_islands = 0;
for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1') {
++num_islands;
dfs(grid, r, c);
}
}
}
return num_islands;
}
}
题解的方法二用的是广度优先搜索,如果gird[i][j]等于1就把它放到一个队列里面,然后不断的从队列中取出元素把grid置为0,每取出一个就把这个元素的上下左右放进队列(当然需要这些元素的grid为1),值得注意的是它放进队列的是这个元素在数组中的序号,也就是行号*每行的个数+列号,所以这个队列中的这个序号被取出来后会通过模运算算出行号和列号,方便找上下左右4个元素。以下是题解方法二的代码:
class Solution {
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
int nr = grid.length;
int nc = grid[0].length;
int num_islands = 0;
for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1') {
++num_islands;
grid[r][c] = '0';
Queue<Integer> neighbors = new LinkedList<>();
neighbors.add(r * nc + c);
while (!neighbors.isEmpty()) {
int id = neighbors.remove();
int row = id / nc;
int col = id % nc;
if (row - 1 >= 0 && grid[row-1][col] == '1') {
neighbors.add((row-1) * nc + col);
grid[row-1][col] = '0';
}
if (row + 1 < nr && grid[row+1][col] == '1') {
neighbors.add((row+1) * nc + col);
grid[row+1][col] = '0';
}
if (col - 1 >= 0 && grid[row][col-1] == '1') {
neighbors.add(row * nc + col-1);
grid[row][col-1] = '0';
}
if (col + 1 < nc && grid[row][col+1] == '1') {
neighbors.add(row * nc + col+1);
grid[row][col+1] = '0';
}
}
}
}
}
return num_islands;
}
}
题解的方法三采用的是并查集的方法,这个方法有点复杂,先上代码:
class Solution {
class UnionFind {
int count;
int[] parent;
int[] rank;
public UnionFind(char[][] grid) {
count = 0;
int m = grid.length;
int n = grid[0].length;
parent = new int[m * n];
rank = new int[m * n];
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (grid[i][j] == '1') {
parent[i * n + j] = i * n + j;
++count;
}
rank[i * n + j] = 0;
}
}
}
public int find(int i) {
if (parent[i] != i) parent[i] = find(parent[i]);
return parent[i];
}
public void union(int x, int y) {
int rootx = find(x);
int rooty = find(y);
if (rootx != rooty) {
if (rank[rootx] > rank[rooty]) {
parent[rooty] = rootx;
} else if (rank[rootx] < rank[rooty]) {
parent[rootx] = rooty;
} else {
parent[rooty] = rootx;
rank[rootx] += 1;
}
--count;
}
}
public int getCount() {
return count;
}
}
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
int nr = grid.length;
int nc = grid[0].length;
int num_islands = 0;
UnionFind uf = new UnionFind(grid);
for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1') {
grid[r][c] = '0';
if (r - 1 >= 0 && grid[r-1][c] == '1') {
uf.union(r * nc + c, (r-1) * nc + c);
}
if (r + 1 < nr && grid[r+1][c] == '1') {
uf.union(r * nc + c, (r+1) * nc + c);
}
if (c - 1 >= 0 && grid[r][c-1] == '1') {
uf.union(r * nc + c, r * nc + c - 1);
}
if (c + 1 < nc && grid[r][c+1] == '1') {
uf.union(r * nc + c, r * nc + c + 1);
}
}
}
}
return uf.getCount();
}
}
它是采用了一个内部类UnionFind,这个类有一个count属性,表示岛屿的个数,parent[]数组,大小就是giad的数组的大小,grid的每个元素都在parent中有对应的位置,也是采用序号的方式(行号*每行的个数+列号),比如grid[i][j]在parent中对应的下标就是parent[i*每行个数+j],它表示grid[i][j]有那个序号的元素连接而找到,通过
public int find(int i) {
if (parent[i] != i) parent[i] = find(parent[i]);
return parent[i];
}
就可以找到序号i元素的最大祖先,然后通过
public void union(int x, int y) {
int rootx = find(x);
int rooty = find(y);
if (rootx != rooty) {
if (rank[rootx] > rank[rooty]) {
parent[rooty] = rootx;
} else if (rank[rootx] < rank[rooty]) {
parent[rootx] = rooty;
} else {
parent[rooty] = rootx;
rank[rootx] += 1;
}
--count;
}
}
rank是一个和gird等大的数组,它表示rank[序号]所在树的深度,
假设grid[i][j]的序号是x,他的相邻元素的序号是y,通过find方法分别找到x和y的根节点rootx和rooty,如果rootx和rooty不相等说明他们之前在两颗独立的树上,因为x和y是相邻的,所以他们其实在同一颗树上,所以他们的树的深度是两颗树深度最大的一个,大的那个root是小的root的parent;如果两颗树的深度相等,那么可以把其中一个root挂在另一个root的叶子上,那么树的深度就+1了,因为他们两个树之前是独立的,但其实他们是一起的也就是说之前count多加了一次,所以count要-1,
只要在numIslands中把grid的每个节点遍历一次,最后返回count即可。