java 岛屿个数问题的时间复杂度

c9qzyr3d  于 2023-06-04  发布在  Java
关注(0)|答案(2)|浏览(189)

这个问题在StackOverflow上已经被问过好几次了,但我还是再问一次,因为这个问题没有得到很好的回答或解释。
LeetCode上有一个问题,链接在这里:LeetCode Question Link
在那里你必须找到一个矩阵上的岛的数量(表示为“1”)。
这是我对这个问题的迭代深度优先搜索(DFS)解决方案:

public int numIslands(char[][] grid) {
    Stack<Integer> stack = new Stack<>();
    int counter = 0;
    int northBound = 0;
    int eastBound = grid[0].length - 1;
    int southBound = grid.length - 1;
    int westBound = 0;
    
    for (int i = 0; i < grid.length; i++) {
        for (int j = 0; j < grid[0].length; j++) {
            if (grid[i][j] == '1') {
                stack.add(i);
                stack.add(j);
                counter += 1;

                while (stack.size() > 0) {
                    int y = (int) stack.pop();
                    int x = (int) stack.pop();
                    grid[x][y] = '0';

                    if (x-1 >= northBound && grid[x-1][y] == '1') {
                        stack.push(x-1);
                        stack.push(y);
                    }

                    if (y+1 <= eastBound && grid[x][y+1] == '1') {
                        stack.push(x);
                        stack.push(y+1);
                    }

                    if (x+1 <= southBound && grid[x+1][y] == '1') {
                        stack.push(x+1);
                        stack.push(y);
                    }

                    if (y-1 >= westBound && grid[x][y-1] == '1') {
                        stack.push(x);
                        stack.push(y-1);
                    }
                }
            }
        }
    }

    return counter;
}

大多数人使用递归解决方案,但似乎普遍的共识是DFS解决这个问题的时间复杂度是O(m * n),其中m是矩阵中的行数,n是列数。
时间复杂度是O((n)),所以我的算法是O(n)的。首先,我们可以看到两个外部for循环访问每个矩阵元素一次,所以这是O(m * n)。然而,在两个外部for循环访问的每个元素处,它可以访问1到m * n个其他元素之间的任何位置,这取决于具有1的相邻元素的数量。因此,复合时间复杂度可以是O((m*n) * (m*n))
我的复杂性分析不正确的原因是什么?为什么其他人忽略了最内层while循环的复杂性?谢谢

5ktev3wc

5ktev3wc1#

你的分析不正确。这是一个有趣的例子,因为这是相当棘手的,但它确实是O(m*n)。让我们先试着用直觉来理解这是怎么回事,而不需要进行严格的证明。
如果Map上有一些小岛,你会“填岛”很多,但每个填岛操作都非常快:在你的“1”周围根本不会有任何陆地,所以你把坐标推到堆栈中,然后通过“弹出它并扫描邻居;任何相邻的地被添加到堆栈的歌舞例程中,该例程仅经历一次,因为没有新的地要添加。因此,您将执行大约O(m*n)的landscan,但每个landscan都具有O(1)的性能,总共只有O(m*n)
另一方面,如果Map是1或2个非常大的岛屿,你的“岛屿填充”要贵得多。大约O(m*n)在一个巨大的土地(输入是 * 所有 * 1个值)的明显情况下更昂贵。但是,您在这里执行的landscan数量是O(1),因此它仍然只加起来是O(m*n)
也许你已经意识到棘手的部分:这两件事是平衡的-最中等的输入(如,既不是“一知半解的小岛”,也不是“一个巨大的岛屿”,而是一些中等大小的岛屿)有O(sqrt(m*n))垃圾填埋场的性能要做,每个垃圾填埋场的性能特征,粗略地说,也是O(sqrt(m*n))。把它们相乘,你就回到了O(m*n)
严格证明这一点有点困难。这可以用big-O符号来实现。诀窍是:最坏情况的概念不能必然被分割成单独的部分。

  • 是的,对于任何给定的陆地扫描,最坏的情况是O(m*n),当然。
  • 是的,你需要通过的单元格数量,最坏的情况下,是O(m*n),当然。

通常情况下,你可以把它们相乘就可以了。但是,如果最坏的情况是平衡的(如果一个子部分表现出最坏的情况保证另一个最终表现出最好的情况),那么这可能是一个过于草率的结论。这取决于它们之间的关系:如果试图平均情况下他们都给你一半,你得到O(½ m*n) * O(½ mn)which is, of course, still O((mn)²`一旦你放弃常数因子。但在这种情况下,“中间”的情况下平方根和相同的“数量级”,因此这确实意味着你不能把它们相乘。
弄清楚这一点确实很棘手。

raogr8fs

raogr8fs2#

算法复杂度为O(n)。
你是对的,外部循环访问每个元素一次,所以成本是O(mn)。
内部的while循环也被启动了mn次,并且 * 有时 * 它在启动后可能会进行O(mn)次迭代,但是这个循环总共有多少次迭代 *?如果使用mn次开始时间O(mn)次迭代进行估计,则会忽略一个重要的考虑因素。
while循环的每次迭代都将1更改为0。由于在矩阵中可以存在至多mn个1,因此while循环迭代的总数可以是至多mn all together

相关问题