前端刷题路-Day29:岛屿数量(题号200)

275 阅读1分钟

岛屿数量(题号200)

题目

给你一个由'1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

示例 1:

输入:grid = [  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
输出:1

示例 2:

输入:grid = [  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]
输出:3

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 300
  • grid[i][j] 的值为 '0' 或 '1'

链接

leetcode-cn.com/problems/nu…

解释

这波啊,这波是一头雾水。

说实话,没有接触过此类题目的人应该很难想到解法吧,反正笔者这里是做不到的。

后来看看了思路,才费劲吧啦的写出来两种解决方案

具体的解释放在代码里,👇:

自己的答案(泛洪算法--FloodFill)

刚看到这个名字的时候是懵逼的,看上去很难懂的样子啊,其实并不是🐶。

它的原理其实很简单,放到这一题里来说是这样的,由于地图里0是水,1是陆地。并且陆地是可以和上下左右的陆地相互连接,那么这里可以在获取到第一块陆地的时候把它改成0,然后遍历它上下左右的块,如果是1就把它改成0,继续找它周围的块,看看有没有陆地,如此遍历或者递归下去即可。

对了,别忘了在每次递归的开始前进行累计统计。

在所有的点都遍历完成后,此时所有的点应该都是0,因为所有的1都被改成0了,这也就是所谓的泛洪算法,就跟洪水一样把把所有的陆地的淹掉,放在这题里简直再合适不过了。

👇看看代码:

var numIslands = function(grid) {
  var lenI = grid.length - 1
      lenJ = grid[0].length - 1
      loop = [-1, 1]
      count = 0
  function findNeighbor(i, j) {
    if (i < 0 || i > lenI) return
    if (j < 0 || j > lenJ) return
    if (grid[i][j] === '0') return
    grid[i][j] = '0'
    for (let k = 0; k < loop.length; k++) {
      findNeighbor(i + loop[k], j)
      findNeighbor(i, j + loop[k])
    }
  }
  for (let i = 0; i <= lenI; i++) {
    for (let j = 0; j <= lenJ; j++) {
      if (grid[i][j] === '1') {
        count++
        findNeighbor(i, j)
      }      
    }    
  }
  return count
};

整体逻辑很简单,有一个递归的函数findNeighbor来把所有的相关陆地都干掉,在递归开始前也累计了count,用来返回最后的值。

findNeighbor内部的实现很简单,首先判断边界值,如果过界了直接返回,如果本身就是水也直接返回。

之后就是拿上下左右的数据,这里用了一个loop数组来对进行相邻块的获取,避免了调用四次findNeighbor的丑陋代码。当然了,这里如果把loop改为一个二维数组仅仅需要调用一次findNeighbor

别的也就没了,这种写法也是比较简单的。

自己的答案(并查集)

关于并查集的概念其实也并不是很复杂,有个老哥说的不错,这里放个链接了,不再赘述,点击这里,这老哥的代码Java的,可以不看,主要看它的概念,说的真的很明白,而且很有趣。

看完这个概念之后就很容易理解了,这里在第一次循环时把每块陆地的parent都设置为陆地自己,之后开始第二次遍历,如果当前是陆地,并且陆地周围也有陆地,那么就把两块陆地连接在一起,如果到最后就可以得到一个完美的并查集。

统计陆地数量的话,可以放在并查集的内部,每次addSetcount加1,之后连接两块陆地时减1即可。

话不多说,看代码👇:

class UnionFind {
  constructor() {
    this.parents = new Map()
    this.count = 0
  }
  addSet(location) {
    location = location.toString()
    this.parents.set(location, location)
    this.count++
  }
  findSet(location) {
    location = location.toString()
    while (this.parents.get(location) !== location) {
      location = this.parents.get(location)
    }
    return location
  }
  unionSet(location1, location2) {
    var location1Parent = this.findSet(location1)
    var location2Parent = this.findSet(location2)
    if (location1Parent === location2Parent) return
    this.parents.set(location1Parent, location2Parent)
    this.count--
  }
  getCount() {
    return this.count
  }
}

function numIslands(grid) {
  var len1 = grid.length
      len2 = grid[0].length
      uf = new UnionFind()
      loop = [1, -1]
  for (let i = 0; i < len1; i++) {
    for (let j = 0; j < len2; j++) {
      if (grid[i][j] === '1') uf.addSet([i, j])
    }
  }
  for (let i = 0; i < len1; i++) {
    for (let j = 0; j < len2; j++) {
      if (grid[i][j] === '1') {
        for (let k = 0; k < loop.length; k++) {
          if (grid[i + loop[k]] && grid[i + loop[k]][j] === '1') {
            uf.unionSet([i, j], [i + loop[k], j])
          }          
          if (grid[i][j + loop[k]] && grid[i][j + loop[k]] === '1') {
            uf.unionSet([i, j], [i, j + loop[k]])
          }  
        }
        
      }
    }
  }
  return uf.getCount()
}

这里代码比较长啊,首先是一个UnionFind类的定义,这就是这里的并查集。

内部实现也比较简单,👇依次看看:

  • constructor

    没啥可说的,定义了parentscount两个变量

  • addSet

    每次添加节点的时候,都将节点的parent设置为自己,这里也就是对象的key对应着value,之后count自增一

  • findSet

    这个方法就是用来找到节点顶部的parent,这里用一个while来进行遍历查找,层层递进找到最顶端的parent

  • unionSet

    这方法用来连接两个节点,先找到两个节点对应的顶端parent,如果它们是一个parent,那就不管了,如果不是,随便把一个节点的顶端parent设置为另外一个节点的顶端parent即可。

    最后count自减1,因为两个节点连在一起了,等于去掉一个节点。

  • getCount

    最后拿到count返回即可。

之后的逻辑也和前面说的一样,首先遍历一次数组,拿到所谓陆地元素,并且插入到并查集当中。此时并查集中拥有所有单块陆地的元素,并且它们的parent都是自己。

之后进行第二次遍历,此时开始对并查集进行连接操作,每连接一块陆地,总数count减去1,如此操作完所有的单个陆地元素,即可得到最后的结果,返回即可。

小结

这里虽然给出了并查集的解法,但并不是一种好的方法,性能和泛洪算法差得太多,泛洪算法的内存占用和时间可以都可以排到前95%以上,但是并查集的解法只能到5%左右,很差很差,当然了,也是因为笔者写的不好,看到一个只循环一次的并查集解法,看起来不错,这里贴个链接,可以参考。



PS:想查看往期文章和题目可以点击下面的链接:

这里是按照日期分类的👇

前端刷题路-目录(日期分类)

经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇

前端刷题路-目录(题型分类)

有兴趣的也可以看看我的个人主页👇

Here is RZ