返回

再别“简单”岛屿题,耗时耗力道阻且长

后端

827 最大人工岛:初探 LeetCode 难题的绕圈魅力

在 LeetCode 浩瀚的题海中,827 最大人工岛题型可谓独树一帜,以其绕口令般的题干和独特的解题思路,让无数程序员又爱又恨。它是一道考验算法、数据结构和思维灵活性的综合题,堪称 LeetCode 中的绕圈王。

难题之魅:绕的艺术

827 最大人工岛的魅力,首先体现在它的题干绕口令般的上。它要求你计算一个由 0 和 1 组成的二维网格中,将一片水域(0)转变为陆地(1)后,最大的岛屿面积。初看题目,似乎有点云里雾里,让人摸不着头脑。

这种绕口令般的,恰恰反映了该题目的独特之处。它没有直接给出解题公式,而是通过迂回曲折的语言,让求解者需要花更多的时间理解题意。但这也正是这类题目吸引人的地方,它挑战了我们的思维惯性,迫使我们跳出常规框架,寻找更具创意的解法。

解题之法:树与并查集的巧妙结合

要解开 827 最大人工岛的绕圈,我们需要借助数据结构的强大力量。具体来说,本题的解题关键在于将网格转化为树,并使用并查集找出最大的连通分量。

1. 网格转树

第一步,我们将网格中的所有相邻陆地连接起来,形成一棵树。这样,每个陆地都成为树上的一个节点,而相邻陆地之间通过树枝相连。

2. 找出最大连通分量

接下来,我们需要找出这棵树中面积最大的连通分量。连通分量是指树中一组相互连接的节点,它们可以互相到达。我们使用并查集算法,将树中的所有节点按连通性分组,并找出最大的那个连通分量。

3. 水域变陆地

最后,我们在最大连通分量中选择一个节点,将其从水域(0)变成陆地(1)。这样,我们就可以扩大最大连通分量的面积。

代码实现:并查集大展身手

基于上述解题思路,我们可以编写代码来解决 827 最大人工岛问题。核心部分使用并查集算法来管理树中的节点。

    private int row;
    private int col;
    private int[][] grid;
    private int[] parent;

    public int largestIsland(int[][] grid) {
        this.grid = grid;
        row = grid.length;
        col = grid[0].length;
        parent = new int[row * col];
        // 初始化并查集
        for (int i = 0; i < row * col; i++) {
            parent[i] = i;
        }
        // 遍历网格,将相邻的陆地合并成一个树
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                if (grid[i][j] == 1) {
                    // 左边有陆地,合并
                    if (j > 0 && grid[i][j - 1] == 1) {
                        union(i * col + j, i * col + j - 1);
                    }
                    // 上方有陆地,合并
                    if (i > 0 && grid[i - 1][j] == 1) {
                        union(i * col + j, (i - 1) * col + j);
                    }
                }
            }
        }
        // 找出最大的连通分量
        int maxSize = 0;
        int[] size = new int[row * col];
        for (int i = 0; i < row * col; i++) {
            int root = find(i);
            size[root]++;
            maxSize = Math.max(maxSize, size[root]);
        }
        // 在最大连通分量中选择一个点,把这个点从陆地变成海洋
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                if (grid[i][j] == 0) {
                    // 检查这个点是否在最大连通分量中
                    int root = find(i * col + j);
                    if (size[root] == maxSize) {
                        // 如果在,把这个点变成陆地,并更新最大连通分量
                        grid[i][j] = 1;
                        maxSize++;
                        break;
                    }
                }
            }
        }
        // 将树还原成网格
        return maxSize;
    }

    private int find(int x) {
        while (x != parent[x]) {
            parent[x] = parent[parent[x]];
            x = parent[x];
        }
        return x;
    }

    private void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX != rootY) {
            parent[rootX] = rootY;
        }
    }

结语:绕的启示

827 最大人工岛难题,让我们深刻体会到了绕的魅力。它证明了在算法求解中,绕圈子有时并非坏事,反而能激发我们的思维火花,促使我们寻找更灵活、更具创意的解法。

解决这类难题,不仅需要扎实的算法基础和数据结构知识,更需要灵活的思维和不屈不挠的探索精神。希望这篇文章能给大家带来一些启发,帮助大家在算法学习的道路上不断突破自我。

常见问题解答

1. 为什么需要将网格转化为树?

将网格转化为树,可以将二维网格问题转化为一维树问题,简化了求解过程。通过树形结构,我们可以方便地找到连通分量,并对节点进行操作。

2. 如何高效地找到最大连通分量?

使用并查集算法,我们可以高效地找到最大连通分量。并查集算法通过维护一个父节点数组,将树中的节点按连通性分组。我们可以通过查找节点的父节点,快速找到其所在连通分量。

3. 为什么选择在最大连通分量中转换一个水域为陆地?

因为最大连通分量已经包含了最大的陆地集合,将水域转换为陆地,可以扩大最大连通分量的面积,从而增加最大岛屿的面积。

4. 如何处理边界情况?

如果网格的边界上有一片陆地,我们需要在网格外添加一个虚拟节点,将其作为这片陆地的父节点。这样,我们可以保证所有的陆地都属于一个连通分量。

5. 时间复杂度是多少?

该算法的时间复杂度为 O(MN),其中 M 和 N 分别为网格的行和列数。它需要遍历整个网格,执行并查集操作,以及处理边界情况。