簡體   English   中英

確定這個程序的大 O 時間復雜度?

[英]Determining the Big O time complexity for this program?

我正在分析我寫給 Leetcode 問題的解決方案的時間復雜度,二叉樹的垂直順序遍歷

該問題提供了一個二叉樹並要求返回一個列表列表,其中每個列表對應於樹中的垂直列,並且每個列表從上到下排序,同一行和列中的值按數字排序。

我的代碼如下所示,但總結一下,我使用兩個隊列逐級執行廣度優先搜索。 我同樣使用兩張地圖。 一個 map 保存整個表的列和該列中的值之間的關系,其他映射具有類似的目的,但它一次只保存一個級別的值。 這使我們能夠對每個級別內的潛在聯系進行排序,然后將其值添加到上述 map。 在程序結束時,包含所有級別信息的 map 將轉換為 Leetcode 分級器的列表列表。

我無法分析排序邏輯的時間復雜度。 最壞的情況是在完全二叉樹中排序最多的情況。 到目前為止,這是我一直在想的:

對於這個完整的二叉樹,節點已經用它們各自的列進行了標記,在頂部,我顯示了每一列在每個級別的頻率。 我在圖像上有這些數據,因為 Stack Overflow 在提交這篇文章后沒有正確呈現表格。

列值的大小並不是特別重要,但出現的頻率模式是帕斯卡三角/二項式系數展開的模式。 事后看來,從右/左子節點的父節點列中添加/減去 1 是有道理的。 列號的頻率表示必須排序的該級別的列表大小(瓶頸)。

鑒於此,我可以重復

遞歸中的 L 指的是二叉樹中的層級。 每個級別的總工作量是之前級別完成的工作量,加上在該級別對具有可變大小的列表進行排序的工作(大小相對於二項式展開系數是可變的)。 從這里開始,我不確定 go 在哪里。 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 個節點組成的二叉樹中有 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) 在技術上仍然是正確的。

這是非常好的。 這是一個有趣的問題。

我只想指出,您可以使用已排序的數據結構進行更嚴格的編碼。 當然,樹圖要慢一些。

class Solution {
  private final TreeMap<Integer, TreeMap<Integer, List<Integer>>> accumulator =
      new TreeMap<>();

  private void add(int val, int i, int j) {
    accumulator.computeIfAbsent(j, k -> new TreeMap<>())
        .computeIfAbsent(i, k -> new ArrayList<>())
        .add(val);
  }

  private void search(TreeNode node, int i, int j) {
    if (node == null) return;
    add(node.val, i, j);
    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());
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM