【问题标题】:Time Complexity of DFS in logest increasing path in a matrix矩阵中最长递增路径中 DFS 的时间复杂度
【发布时间】:2023-03-12 16:48:01
【问题描述】:

我遇到了一个问题,要在矩阵中找到最长的递增路径。蛮力解决方案非常简单:

public class Solution {

  private static final int[][] dirs = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
  private int m, n;

  public int longestIncreasingPath(int[][] matrix) {
      if (matrix.length == 0) return 0;
      m = matrix.length;
      n = matrix[0].length;
      int ans = 0;
      for (int i = 0; i < m; ++i)
          for (int j = 0; j < n; ++j)
              ans = Math.max(ans, dfs(matrix, i, j));
      return ans;
  }

  private int dfs(int[][] matrix, int i, int j) {
      int ans = 0;
      for (int[] d : dirs) {
          int x = i + d[0], y = j + d[1];
          if (0 <= x && x < m && 0 <= y && y < n && matrix[x][y] > matrix[i][j])
              ans = Math.max(ans, dfs(matrix, x, y));
      }
      return ++ans;
  }
}

时间复杂度为O(2^(m+n)),其中 m 为否。行数,n 为否。矩阵中的列数。

我很难理解这一点。第一个嵌套的 for 循环是 O(mn),这很好。现在每个单元都被视为一个根,并对其进行 DFS。但是一个DFS的时间复杂度是O(V + E),这里是V = mn and E = 4*mn,所以每个dfs应该是O(mn),所以总的时间复杂度应该是O(mn) x O(mn) = O(m^2.n^2)吧?

注意:我知道这不是最佳解决方案,可以记住,但我的问题是关于理解这种粗暴方法的时间复杂度。

【问题讨论】:

    标签: java time-complexity depth-first-search


    【解决方案1】:

    您的 DFS 不是 O(V+E) = O(nm),因为您没有访问集。如果没有这个集合,您的各个分支所采用的路径可能会重叠和重复工作,这样您就可以从来自longestIncreasingPath 的任何给定 DFS 调用中探索相同的顶点并多次遍历相同的边。分支因子为 4 的无记忆搜索是导致指数行为的原因。

    例如,考虑完美凸矩阵的潜在最坏情况:

    6 5 4 3 4 5 6
    5 4 3 2 3 4 5
    4 3 2 1 2 3 4 
    3 2 1 0 1 2 3
    4 3 2 1 2 3 4
    5 4 3 2 3 4 5
    6 5 4 3 4 5 6
    

    您搜索的任何顶点的最坏情况路径是沿对角线爬到最近的拐角处,这样的路径长 O((n+m)/2) 步。每个顶点最多有 4 个选项,并且由于在递归调用之间没有以访问集形式存在的共享内存,因此您会得到一个天真的 4^((n+m)/2) = 2^(n+m) DFS 的案例复杂度。更准确地说,在这种最坏情况下,大多数顶点只有 2 到 3 个可行的邻居可以递归到,这样每次搜索的实际最坏情况复杂度将在 sqrt(2)^(n+m) 之间和 sqrt(3)^(n+m),但指数运行时间是一样的。

    如果您有一个访问过的集合,您会得到一个更像您在答案中提到的那个的复杂性。由于路径增加的限制,它将是 O(nm*((n+m)/2)) = O(n^2*m + m^2*n),但如果没有该限制,它将是 O( (nm)^2)。

    【讨论】:

    • 另请注意:我相信所提供算法的真正最坏情况复杂度为 O(nm*2^(n+m)) 因为您在矩阵的每个节点上运行 DFS,但是这并没有太大的区别。
    • Memoization 将具有与包含已访问集相同的实际效果。
    • 感谢您的精彩解释!它开始变得有意义了。关于 O((n+m)/2) 仍然有点不清楚。这是否意味着如果我画一棵树,其节点有 4 个孩子,那么这样的树的深度将为 O((n+m)/2)?
    • @Ufder 基本上,是的。您可以将 DFS 分析为 O(b^d) 算法,其中b 是平均分支因子,d 是平均深度。该算法的最坏情况平均值b 值是在我给出的示例中的凸矩阵中获得的,其中除了矩阵边缘的节点之外的每个节点都有 DFS 可以采用的多个分支。 d 值作为此类矩阵的平均 DFS 深度为 O((n+m)/2) = O(n+m),因为从任何节点到其局部梯度顶部的路径最多为 ( n+m)/2(与中心顶点一样)。
    • @Ufder 我不会说我是专家,但是学习了一些算法和数据结构课程,当然在学习基本概念和技术方面有所帮助,但更重要的是练习为自己证明这些事情。我认为,当你在学习算法(或任何数学知识)时,练习解释为什么结果成立或算法为何有效,并在可能的情况下正式向自己证明它,会在长期,因为它比简单地记住任何特定结果更能概括理解新问题。
    猜你喜欢
    • 2020-03-06
    • 2018-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-05
    • 1970-01-01
    • 2019-11-04
    相关资源
    最近更新 更多