青云算法面试题干货-生成最大的岛屿

问题:输入一个由数字0或1组成的二维数组,其中0表示海洋,而水平或竖直方向连通的1组成的区域为岛屿。岛屿的面积为组成岛屿的1的数目。如果最多能把数组中的一个0变成1,请问能得到的最大的岛屿的面积是多少?

例如,输入数组[[1, 0], [0, 1]],把其中任意一个0变成1都能将已有的两个面积为1的岛屿连接起来,形成一个面积为3的岛屿。

分析:这是LeetCode的第827题。 

另一个经典面试题的加强版是:输入一个由数字0或1组成的二维数组,其中0表示海洋,而水平或竖直方向连通的1组成的区域为岛屿。请问输入的数组中有多少个岛屿?

把数组中的1看成是图的节点。如果一个1与其他的1在水平或竖直方向相邻,那么,相当于图中对应的两个节点有边相连。因此,把找岛屿的过程转换成图的遍历:从任意一个1出发,遍历和它所有相连的1,从而遍历整个岛屿上的所有1。当然,我们在遍历岛屿的同时可以统计岛屿里的1的数目即岛屿的面积。

我们每遍历了一个值为1的节点,用一个布尔值标记这个节点已经被遍历过了。当一个岛屿上所有的1都被遍历完之后,可以去找下一个还没有被遍历到的值为1的节点。一旦找到一个还没有被遍历到的值为1的节点,就意味着又找到了一个之前没有到达的岛屿,于是接下来,我们遍历这个岛屿上的所有1。然后寻找下一个还没有被遍历到的值为1的节点。一直重复这个过程直到输入数组中所有1都被遍历为止。

这个过程可以用下面的Java代码来实现:

public int largestIsland(int[][] grid) {
    if (grid.length == 0 || grid[0].length == 0) {
        return 0;
    }

    int[][] islands = new int[grid.length][grid[0].length];
    Map<Integer, Integer> islandToSize = new HashMap<>();
    boolean[][] visited = new boolean[grid.length][grid[0].length];
    int island = 0;
    for (int i = 0; i < grid.length; ++i) {
        for (int j = 0; j < grid[0].length; ++j) {
            if (!visited[i][j] && grid[i][j] == 1) {
                bfs(grid, islands, i, j, island++, islandToSize, visited);
            }
        } 
    }

    return largestMergedSize(grid, islands, islandToSize);
}

通常有两种不同的算法来遍历一个连通图的所有节点,即深度优先搜索和广度优先搜索算法。这里应用任何一种遍历算法都可以解决这个面试题。下面的函数bfs基于广度优先搜索算法。

static int[][] dirs = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}};

private void bfs(int[][] grid, int[][] islands, int i, int j, int island,
                 Map<Integer, Integer> islandToSize, boolean[][] visited) {
    Queue<int[]> queue = new LinkedList<>();
    queue.offer(new int[] {i, j});
    visited[i][j] = true;
    int size = 0;
    while (!queue.isEmpty()) {
        int[] pos = queue.poll();
        islands[pos[0]][pos[1]] = island;
        size++;

        for (int[] dir : dirs) {
            int x = pos[0] + dir[0];
            int y = pos[1] + dir[1];
            if (x >= 0 && x < grid.length && y >= 0 && y < grid[0].length
               && grid[x][y] == 1 && !visited[x][y]) {
                queue.offer(new int[] {x, y});
                visited[x][y] = true;
            }
        }
    }

    islandToSize.put(island, size);
}

除了找出二维数组中的所有岛屿,我们还需要合并和某一个0相邻的一个或多个岛屿。一个0最多与周围4个1相邻,我们需要知道每个相邻的1属于哪个岛屿,以及这些岛屿的面积是多少。

在上述函数dfs中,参数island是当前遍历的岛屿的标号。我们每遍历到一个值为1的节点,就把该节点的岛屿标号存入到二维数组islands中。我们一边遍历该岛屿,一边统计该岛屿的面积,最后把岛屿的面积存入到哈希表islandToSize中。这个哈希表的键值是岛屿的标号,值是岛屿的面积。

有了每个值为1的节点的岛屿标号以及每个岛屿的面积,我们就可以合并与0相邻的岛屿了。我们找出与0相邻的值为1的节点。针对每一个值为1的节点,在二维数组islands中找出它的岛屿标号,然后在哈希表islandToSize中找出岛屿的面积。有几个岛屿和这个0相邻,我们就把这几个岛屿合并。

合并的代码如函数largestMergedSize所示:

private int largestMergedSize(int[][] grid, int[][] islands,
                             Map<Integer, Integer> islandToSize) {
    int maxSize = islandToSize.getOrDefault(0, 0);
    for (int i = 0; i < grid.length; ++i) {
        for (int j = 0; j < grid[0].length; ++j) {
            if (grid[i][j] == 0) {
                int[] sizes = new int[dirs.length];
                Set<Integer> neighborIslands = new HashSet<>();
                for (int k = 0; k < dirs.length; ++k) {
                    int x = i + dirs[k][0];
                    int y = j + dirs[k][1];
                    if (x >= 0 && x < grid.length && y >= 0 && y < grid[0].length
                        && grid[x][y] == 1 && !neighborIslands.contains(islands[x][y])) {
                        sizes[k] = islandToSize.get(islands[x][y]);
                        neighborIslands.add(islands[x][y]);
                    }
                }

                int newSize = 0;
                for (int size : sizes) {
                    newSize += size;
                }

                maxSize = Math.max(maxSize, newSize + 1);
            }
        }
    }

    return maxSize;
}

上述代码中还有一个细节值得注意,和某个0相邻的多个1可能属于同一个岛屿。因此,上述代码用了一个类型为HashSet的变量neighborIslands来存储和某个0相邻的所有岛屿,确保在计算合并岛屿面积的时候每个岛屿只计算一次。

最后分析时间复杂度。上述解法分为两步。第一步是遍历岛屿上所有值为1的节点。假设二维数组的长和宽都是n,则图中可能有O(n^2)个节点,每个节点最多有4条边。图遍历的时间复杂度是O(V+E),V是图中的节点总数,E是边的总数,因此第一步遍历的时间复杂度是O(n^2)。

第二步是合并。函数largestMergedSize扫描二维数组中的每个0并尝试合并与当前扫描到的0相邻的岛屿。可以用O(1)时间找出和0相邻的岛屿并得出对应岛屿的面积,因此合并的时间复杂度也是O(n^2)。

所以这个解法总的时间复杂度是O(n^2)。

猜你喜欢

转载自blog.csdn.net/QingyunAlgo/article/details/80458643