【问题标题】:Determining the Big O time complexity for this program?确定这个程序的大 O 时间复杂度?
【发布时间】:2022-01-07 00:59:39
【问题描述】:

我正在分析我写给 Leetcode 问题的解决方案的时间复杂度,Vertical Order Traversal of a Binary Tree

该问题提供了一个二叉树,并要求返回一个列表列表,其中每个列表对应于树中的垂直列,并且每个列表从上到下排序,同一行和列中的值按数字排序.

我的代码如下所示,但总结一下,我使用两个队列逐级执行广度优先搜索。我同样使用两张地图。一个映射保存整个表的列和该列中的值之间的关系,而其他映射用于类似的目的,但它一次只保存一个级别的值。这使我们能够对每个级别内的潜在联系进行排序,然后将其值添加到上述地图中。在程序结束时,包含所有级别信息的地图被转换为 Leetcode 评分器的列表列表。

我无法分析排序逻辑的时间复杂度。最坏的情况是在完全二叉树中排序最多的情况。到目前为止,这是我一直在想的:

对于这个完整的二叉树,节点已经用它们各自的列进行了标记,在顶部,我显示了每一列在每个级别的频率。我在图像上有这些数据,因为 Stack Overflow 在提交这篇文章后没有正确呈现表格。

列值的大小并不是特别重要,但出现的频率模式是帕斯卡三角/二项式系数展开的模式。事后看来,从右/左子节点的父节点列中添加/减去 1 是有道理的。列号的频率表示必须排序的该级别的列表大小(瓶颈)。

鉴于此,我可以重复

递归中的L指的是二叉树中的层级。每个级别的总工作量是之前级别完成的工作量,加上在该级别对具有可变大小的列表进行排序的工作(大小相对于二项式展开系数是可变的)。从这里开始,我不知道该去哪里。 L,或 logN,表示树中的 N 个元素。

直觉让我相信这种重复会产生以下表达式

但这仍然不是很清楚,我也不确定它是否正确(就像我说的,直觉---不是证明---导致我这样做)。这看起来对吗?有没有办法简化我还没有看到的?

我在下面附上了实现此功能的 Java 代码。谢谢,如果您有任何问题,请告诉我。

 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 *
 */
class Solution {
    /*
    * Map<TreeNode, Integer> to keep track of column for each treenode
    * Map<Integer, List<Integer>> to keep track of columns and the values in the column
    * Map<Integer, List<Integer>> to keep track of columns and the values in the column
                                  FOR EACH ROW
    * Breadth first search
    * - as adding to queue, add to column map
    * - use a 2 queue approach for each level in breadth first search
    * - when each level of BFS is finished, sort third map, then add all to second map
        and clear it
    */
    public List<List<Integer>> verticalTraversal(TreeNode root) {
      Map<TreeNode, Integer> columns = new HashMap<>();
      final Map<Integer, List<Integer>> table = new HashMap<>();
      Map<Integer, List<Integer>> levelTable;
      
      Queue<TreeNode> bfsQueue;
      Queue<TreeNode> nextLevel = new ArrayDeque<>();
      int minColOverall = Integer.MAX_VALUE;
      
      nextLevel.offer(root);
      columns.put(root, 0);
      while(!nextLevel.isEmpty()){
        bfsQueue = nextLevel;
        nextLevel = new ArrayDeque<>();
        levelTable = new HashMap<>();
        int minCol = Integer.MAX_VALUE;
        while(!bfsQueue.isEmpty()){ // Evaluate each level individually
          TreeNode node = bfsQueue.poll();
          int column = columns.remove(node);
          minCol = Math.min(minCol,column);
          if(!levelTable.containsKey(column))
            levelTable.put(column, new ArrayList<Integer>());
          levelTable.get(column).add(node.val);
          
          if(node.left != null){
            nextLevel.offer(node.left);
            columns.put(node.left, column-1);
          }
          if(node.right != null){
            nextLevel.offer(node.right);
            columns.put(node.right, column+1);
          }
        }
        minColOverall = Math.min(minCol, minColOverall);
        for(int m = minCol; !levelTable.isEmpty(); m+=2){ // After level, add sorted col vals to final table
          if(!levelTable.containsKey(m))
            continue;
          List<Integer> level = levelTable.remove(m);
          Collections.sort(level);
          if(!table.containsKey(m))
            table.put(m, new ArrayList<Integer>());
          table.get(m).addAll(level);
        }
      }
      
      // Transform map to list
      final List<List<Integer>> answer = new ArrayList<>();
      while(!table.isEmpty())
        answer.add(table.remove(minColOverall++));
      return answer;
    }
}```

【问题讨论】:

  • 正如你所说,紧束缚将是一个混乱的总结。我不会尝试解决它,尽管看起来你在正确的轨道上。好消息是您可以确定 O(n log n) 是正确的。很难想象一个实际案例中,这与紧密界限之间的差异将是有意义的。 O(n log n) 甚至可能紧。

标签: algorithm sorting time-complexity big-o


【解决方案1】:

我相信我已经想出了一个上限的解释。

时间复杂度是遍历的时间复杂度加上排序操作的时间复杂度。排序的时间复杂度将支配遍历的时间复杂度,所以我将重点关注这一点。

这个解释的关键是,虽然每个级别的列大小的大小相对于二项式系数展开是可变的,但如果使用平均输入大小代替,则可以忽略这一点。

设时间复杂度为:

O(总排序数 * 平均排序复杂度)

我们对每一行中的每一列进行排序,因此排序的总数是该行中所有行的列数的总和。在由 N 个节点组成的二叉树中有 O(log N) 行。如上图所示,每行中唯一列的数量以 1 线性增长。第一行有 1 个唯一列,第二行有 2 个唯一列,第三行有 3 个唯一列……最后一行,行编号 logN,将有 logN 唯一列。这是从 1 到 logN 的整数的简单线性和,等于 logN(1+logN)/2,即 O((logN)^2)

要排序的输入的总大小等于 N,因为每个节点只属于一列。这意味着平均排序大小为 O(N / (logN)^2)。令 M 的值等于 N / (logN)^2。

这意味着整个时间复杂度

O(总排序数 * 平均排序复杂度) =

O((logN)^2 * M log M) =

O((logN)^2 * (N / (logN)^2) log [N / (logN)^2]) =

O(N log [N / (logN)^2])

这个界限比 O(N log N) 更严格,但 O(N log N) 在技术上仍然是正确的。

【讨论】:

    【解决方案2】:

    真是太好了。这是一个有趣的问题。

    我只想指出,您可以使用已排序的数据结构进行更严格的编码。当然树图要慢一些。

    class Solution {
      private final TreeMap<Integer, TreeMap<Integer, List<Integer>>> accumulator =
          new TreeMap<>();
    
      private void search(TreeNode node, int i, int j) {
        if (node == null) return;
        accumulator.computeIfAbsent(j, k -> new TreeMap<>())
            .computeIfAbsent(i, k -> new ArrayList<>())
            .add(node.val);
        search(node.left, i + 1, j - 1);
        search(node.right, i + 1, j + 1);
      }
    
      public List<List<Integer>> verticalTraversal(TreeNode root) {
        search(root, 0, 0);
        return  accumulator.values().stream().map(colMap -> {
          colMap.values().forEach(Collections::sort);
          return colMap.values().stream().flatMap(List::stream).collect(toList());
        }).collect(toList());
      }
    }
    

    【讨论】:

    • 我一直在尝试在我的代码中使用更多的内置函数和Java Streams,所以我非常感谢这段代码。非常简洁!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-01
    • 1970-01-01
    • 2018-07-08
    • 2014-05-29
    相关资源
    最近更新 更多