【问题标题】:largest complete subtree in a binary tree二叉树中最大的完全子树
【发布时间】:2015-11-21 10:57:47
【问题描述】:

我将完整的子树定义为所有级别都已满且最后一层左对齐的树,即所有节点都尽可能靠左,我想在完整的树中找到最大的子树。

一种方法是将每个节点作为根节点执行here 概述的方法,这将花费 O(n^2) 时间。

有更好的方法吗?

【问题讨论】:

  • 这感觉像是作业,所以这里有一个提示:假设有一棵深度至少为 k 的完整树,其根在顶点 v。想想 v 的子节点必须具有哪些属性:必须为零或至少有 k 个完整级别低于他们的更多儿童,其次是最多 1 个具有至少 k-1 个完整级别的儿童和 1 个部分级别,其次是零个或更多儿童具有至少 k-1 个完整级别。
  • 请说明您是指二叉树还是任意树。
  • 您对子树的定义与子树的实际定义不同,子树是“由 T 中的一个节点及其 T 中的所有后代组成的树”。这是故意的吗,因为 EPI 似乎指的是实际定义?

标签: algorithm


【解决方案1】:

由于上面没有 C++ 解决方案,我添加了我的解决方案。如果您觉得有什么不正确或有什么可以改进的,请告诉我。

struct CompleteStatusWithHeight {
 bool isComplete;
 int height;
};

int FindLargestCompletetSubTreeSize(const unique_ptr<BinaryTreeNode<int>>& tree)
{
  return CheckComplete(tree).height;
}

CompleteStatusWithHeight CheckComplete(const unique_ptr<BinaryTreeNode<int>>& tree)
{
if (tree == nullptr) {
       return {true, -1};  // Base case.
}

auto left_result = CheckComplete(tree->left);
if (!left_result.isComplete) {
  return {false, 0};  // Left subtree is not balanced.
}
auto right_result = CheckComplete(tree->right);
if (!right_result.isComplete) {
  return {false, 0};  // Right subtree is not balanced.
}

bool is_balanced = abs(left_result.height - right_result.height) == 0;
bool is_left_aligned = (left_result.height - right_result.height) == 1;
bool is_leaf =  left_result.height  == -1 && right_result.height ==-1;
bool is_complete = is_balanced || is_left_aligned || is_leaf;

int height = max(left_result.height, right_result.height) + 1;
return {is_complete, height};
}

【讨论】:

    【解决方案2】:

    这是我在 Python 中的解决方案。它正在处理我提出的案例。 返回值的含义如下: [x,y,z]

    • x = 到此节点为止的最大完整子树的大小
    • y = 子树的高度
    • z: 0 - 完整子树,1 - 在这个子树中只有一个左子节点,2 - 不是完整子树

      def largest_complete_tree(root):
      
          result = traverse_complete(root)
          print('largest complete subtree: {}'.format(result[0]))
      
      def traverse_complete(root):
          if root:
              left = traverse_complete(root.left)
              right = traverse_complete(root.right)
              max_complete = max(left[0], right[0])
              max_height = max(left[1], right[1])
              left_child_only = 1 if (left[2] == 1 and right[0] == 0) or (left[0] == 1 and right[0] == 0) else 0
      
              # 5 conditions need to pass before left and right can be joined by this node
              # to create a complete subtree.
              if left[0] < right[0]:
                  return [max_complete, 0, 2]
              if left[2] == 2 or right[2] == 2:
                  return [max_complete, 0, 2]
              if abs(left[1]-right[1]) > 1:
                  return [max_complete, 0, 2]
              if (left[2] == 1 and right[2] == 1) or (left[2] == 0 and right[2] == 1):
                  return [max_complete, 0, 2]
              if left[0] == right[0] and left[0] != 2**left[0] - 1:
                  return [max_complete, 0, 2]
              return [left[0] + right[0] + 1, max_height + 1, left_child_only]
          else:
              return [0,0,0]
      

    【讨论】:

      【解决方案3】:

      定义树节点的等级,如果该节点是根,则作为最大完整子树的高度。 如果该节点是根节点,则将节点宽度定义为最大完整子树的最后一级中的节点数。 所以对于树中的每个节点,我们有两个数字(r, w)。和w &lt;= 2^r

      如果节点有零个或只有一个子节点,则节点有(r, w) = (1, 1)

      如果节点有两个孩子(r1, w1)(r2, w2),我们有几种情况:

      1. r1 &gt; r2 当节点将有 (r2 + 1, 2^r2 + w2)
      2. r1 == r2w1 == 2^r1 当节点将具有 (r1 + 1, w1 + w2)
      3. r1 == r2w1 &lt; 2^r1 当节点将有 (r1 + 1, w1) 示例:
      
               root     
               ....
          /  \     /   \
         l    l    r    r
        /\   /    /\    /
        l l  l    r r  r
      

      最大完全子树是

      
                m     
               ....
          /  \     /   \
         m    m    m    m
        /\   /    /\    /
        m m  m    r r  r
      
      1. r1 &lt; r2w1 == 2^r1 当节点将有 (r1 + 1, 2 * w1) 示例:
      
               root     
               ....
          /  \      /   \
         l    l     r    r
        /\   / \    /\    /\
        l l  l  l   r r  r  r
                   /
                  r
      

      最大完全子树是

      
                m     
               ....
          /  \      /   \
         m    m     m    m
        /\   / \    /\    /\
       m  m  m  m   m m  m  m
                   /
                  r
      
      1. r1 &lt; r2w1 &lt; 2^r1 当节点将有 (r1 + 1, w1)

      例子:

      
               root     
               ....
          /  \      /   \
         l    l     r    r
        /\   /      /\    /\
        l l  l      r r  r  r
                   /
                  r
      

      最大完全子树是

      
                m     
               ....
          /  \      /   \
         m    m     m    m
        /\   /     /\    /\
       m  m  m     r r  r  r
                  /
                  r
      

      基于此规则,您可以使用递归计算每个节点的(r, w)。这将需要O(n)。当您在此节点中找到最大等级为r 的节点时,找到最大为w 的节点,该节点应该是一个解决方案。

      【讨论】:

      • 这没有考虑左对齐条件,例如,如果一个节点有 2 个孩子,并且两个孩子都是完整的,这并不意味着以节点为根的树是完整的,因为可能有是两个子子树之间最后一层的间隙
      • 还要注意问题是针对完整子树而不是完整子树,并注意问题中完整子树的定义
      • 如果对于某个节点,我们从该节点找到一个最大完整树,那么如果最后一级的第一个左节点有子节点,那么最大完整树应该具有相同的高度或更大。
      • 考虑一个简单的例子,假设我有一棵高度为 4 的树,但最后一层中间只有 2 个节点具有相同的父节点,考虑到只有完整的树,我会得到整棵树一棵完整的树,因为我只考虑排名,但它不是
      • 我明白了,我根据这个要求更新算法。
      【解决方案4】:

      我在编写 Elements of Programming Interviews 的一个变体时偶然发现了这篇文章。我想分享我的想法和代码。

      欢迎任何cmets。

      我正在使用递归来解决这个问题。 max 用于存储曾经发生的最大大小(我使用了一个数组,因为 java 是按值计算的)。 返回值信息包含有关树是否 传入的是否是完整的树。仅在完成时返回树大小,否则返回 (-1, false)。 如果子树 T' 不完整,则永远不会选择它的大小来组成更大的完整树。并且所有 T 的子树的大小都会一直记录在 max 中,所以我们不会错过任何值。

      下面是它的工作原理

      • 基本情况:root == null 或 root 是叶子
      • 递归处理左孩子和右孩子。
      • 根据左/右孩子的返回值处理当前树 - leftInfo 和 rightInfo。

      • 如果两者都不完整,则树不完整,无需更新 最大限度。如果任一完成,则树不完整,将 max 更新为 左右的尺寸越大。如果两者都完成,则树 有可能是完整的。首先检查左边是否完美,然后 正好满足身高要求。如果是,则返回 (true, 新尺寸)。否则,树不完整,更新 max 为 left 和 right 的值更大。

      下面是我的代码。应该是时间 O(n) 和空间 O(h),其中 h 是树的高度。(如果它是平衡的,否则最坏的情况将是 O(n))。

       public class Solution {
      
          public static void main(String[] args){
              TreeNode[] trees = new TreeNode[10];
              for(int i = 0; i < 10; i++){
                  trees[i].val = i;
              }
          }
      
          public int largestCompleteTree(TreeNode root){
              int[] max = new int[1];
              helper(root, max);
              return max[0];
          }
      
          private Info helper(TreeNode root, int[] max){
              //Base case:
              if(root == null){
                  return new Info(0, true);
              }
      
              if(root.left == null && root.right == null){
                  max[0] = Math.max(max[0], 1);
                  return new Info(1, true);
              }
      
              //Recursion
              Info leftInfo = helper(root.left, max);
              Info rightInfo = helper(root.right, max);  
      
              //Process based on left subtree and right subtree.
              //Neither is complete.
              if(!leftInfo.isComplete && !rightInfo.isComplete){
                  //Do not need to update the max value.
                  return new Info(-1, false);
              }
              //One of the subtree is complete, the current tree is not complete
              else if(!leftInfo.isComplete || !rightInfo.isComplete){
                  if(leftInfo.isComplete){
                      max[0] = Math.max(max[0], leftInfo.size);
                      return new Info(-1, false);//the value has been recorded
                  }else{
                      max[0] = Math.max(max[0], rightInfo.size);
                      return new Info(-1, false);
                  }
              }
              //Both subtrees are complete,           
              else{
                  int size = 0;
                  if(((rightInfo.size & (rightInfo.size + 1)) == 0 &&
                      leftInfo.size >= rightInfo.size &&
                      leftInfo.size <= rightInfo.size*2 + 1)||
                      ((leftInfo.size & (leftInfo.size + 1)) == 0 &&
                              rightInfo.size >= (leftInfo.size - 1)/2 &&
                              rightInfo.size <= leftInfo.size))
                      {
                          size = leftInfo.size + rightInfo.size + 1;
                          max[0] = Math.max(max[0], size);
                          return new Info(size, true);
                      }
                   else{ //find the subtree with the greater size
                      size = leftInfo.size > rightInfo.size ? leftInfo.size : rightInfo.size;
                      max[0] = Math.max(max[0], size);
                      return new Info(0, false);
                  } 
              }   
          }
          class Info {
              boolean isComplete;
              int size;
      
              public Info(int size, boolean isComplete){
                  this.isComplete = isComplete;
                  this.size = size;
              }
          }
      }
      

      【讨论】:

      • 这也行不通,因为对左子树的严格要求是严格包含节点的 2 次幂,例如考虑一个简单的树,其中根有 2 个孩子,左孩子有另一个左孩子,而右孩子没有孩子,在这种情况下,在根,我们检查左孩子包含4个节点,但它只包含2个,所以我们不会认为它是一棵完整的树,但它是
      • 再次尝试看一下我定义的完整树,除了最后一层,所有层都满了,可以包含任意数量的节点
      • 好点。对于树节点根,它的左右可能都是完整的。判断它们是否能形成一棵完整的树:1)如果右树是完美的,那么leftSize需要满足leftSize >= rightSize && leftSize = leftSize/2 -1。如果任一条件都满足,那么我们可以计算新的大小并更新最大值。
      【解决方案5】:

      在“编程面试要素”一书中遇到了这个任务,我似乎找到了一个非常简单的解决方案,但仍然不确定它是否正确,但在几个案例中进行了测试,并且成功了:

      private struct str
              {
                  public bool isComplete;
                  public int height, size;
                  public str(bool isComplete, int height, int size)
                  {
                      this.isComplete = isComplete;
                      this.height = height;
                      this.size = size;
                  }
              }
      
              public int SizeOfLargestComplete()
              {
                  return SizeOfLargestComplete(root).size;
              }
      
              private str SizeOfLargestComplete(Node n)
              {
                  if (n == null)
                      return new str(true, -1, 0);
                  str l = SizeOfLargestComplete(n.left);
                  str r = SizeOfLargestComplete(n.right);
      
                  if (!l.isComplete || !r.isComplete)
                      return new str(false, 0, Math.Max(l.size, r.size));
      
                  int numberOfLeftTreeLeafes;
                  if (l.height == -1)
                      numberOfLeftTreeLeafes = 0;
                  else
                      numberOfLeftTreeLeafes = l.size - ((1 << l.height) - 1);
      
                  bool leftTreeIsPerfect = (1 << (l.height + 1)) - 1 - l.size == 0;
      
                  //if left subtree is perfect, right subtree can have leaves on last level
                  if (leftTreeIsPerfect)
                      if (l.size - r.size >= 0 && l.size - r.size <= numberOfLeftTreeLeafes)
                          return new str(true, l.height + 1, l.size + r.size + 1);
                      else
                          return new str(false, 0, Math.Max(l.size, r.size));
                  //if left subtree is not perfect, right subtree can't have leaves on last level
                  //so size of right subtree must be the same as left without leaves
                  else
                      if (r.size == l.size - numberOfLeftTreeLeafes)
                      return new str(true, l.height + 1, l.size + r.size + 1);
                  else
                      return new str(false, 0, Math.Max(l.size, r.size));
      
              }
      

      【讨论】:

        【解决方案6】:

        这是我建议的解决方案:要点是跟踪子树的当前节点数、当前高度和最大高度,直到该点为止。

        在当前节点数和高度的情况下,可以通过其直接子节点各自的信息来计算根节点的节点数和高度,同时考虑子节点高度之间的关系以及它们是否是完美子树。

        解是O(n)时间复杂度和O(h)空间复杂度(函数调用栈从根通过唯一路径对应到当前节点)。

        这是此解决方案的 Python 代码,您可以通过示例找到完整的要点here

        from collections import namedtuple
        
        
        class BTN():
            def __init__(self, data=None, left=None, right=None):
                self.data = data
                self.left = left
                self.right = right
        
        # number of nodes for a perfect tree of the given height
        def max_nodes_per_height(height: int) -> int:
            return 2**(height + 1) - 1
        
        
        def height_largest_complete_subtree(root: BTN) -> int:
            CompleteInformation = namedtuple('CompleteInformation', ['height', 'num_nodes', 'max_height'])
        
            def height_largest_complete_subtree_aux(root: BTN) -> CompleteInformation:
                if (root is None):
                    return CompleteInformation(-1, 0, 0)
        
                left_complete_info = height_largest_complete_subtree_aux(root.left)
                right_complete_info = height_largest_complete_subtree_aux(root.right)
        
                left_height = left_complete_info.height
                right_height = right_complete_info.height
        
                if (left_height == right_height):
                    if (left_complete_info.num_nodes == max_nodes_per_height(left_height)):
                        new_height = left_height + 1
                        new_num_nodes = left_complete_info.num_nodes + right_complete_info.num_nodes + 1
                        return CompleteInformation(new_height,
                                                   new_num_nodes,
                                                   max(new_height, max(left_complete_info.max_height, right_complete_info.max_height))
                                                  )
                    else:
                        new_height = left_height
                        new_num_nodes = max_nodes_per_height(left_height)
                        return CompleteInformation(new_height,
                                                   new_num_nodes,
                                                   max(new_height, max(left_complete_info.max_height, right_complete_info.max_height))
                                                  )
                elif (left_height > right_height):
                    if (max_nodes_per_height(right_height) == right_complete_info.num_nodes):
                        new_height = right_height + 2
                        new_num_nodes = min(left_complete_info.num_nodes, max_nodes_per_height(right_height + 1)) + right_complete_info.num_nodes + 1
                        return CompleteInformation(new_height,
                                       new_num_nodes,
                                       max(new_height, max(left_complete_info.max_height, right_complete_info.max_height))
                                      )
                    else:
                        new_height = right_height + 1
                        new_num_nodes = max_nodes_per_height(right_height) + right_complete_info.num_nodes + 1
                        return CompleteInformation(new_height,
                                       new_num_nodes,
                                       max(new_height, max(left_complete_info.max_height, right_complete_info.max_height))
                                      )
        
                elif (left_height < right_height):
                    if (left_complete_info.num_nodes == max_nodes_per_height(left_height)):
                        new_height = left_height + 1
                        new_num_nodes = left_complete_info.num_nodes + max_nodes_per_height(left_height) + 1
                        return CompleteInformation(new_height,
                                   new_num_nodes,
                                   max(new_height, max(left_complete_info.max_height, right_complete_info.max_height))
                                  )
                    else:
                        new_height = left_height
                        new_num_nodes = (max_nodes_per_height(left_height - 1) * 2) + 1
                        return CompleteInformation(new_height,
                                   new_num_nodes,
                                   max(new_height, max(left_complete_info.max_height, right_complete_info.max_height))
                                  )
        
            return height_largest_complete_subtree_aux(root).max_height
        

        【讨论】:

          【解决方案7】:

          我找到了一个类似于 Mikhail 上面的解决方案(在阅读 EPI 书时遇到)。我已经针对一棵完整树、一棵完美树、一棵完整树和带有完整子树的树的一些排列对其进行了测试……但并非详尽无遗。

          /**
           * Returns the largest complete subtree of a binary tree given by the input node
           *
           * @param root the root of the tree
           */
          public int getLargestCompleteSubtree(INode<TKeyType, TValueType> root) {
              max = 0;
              calculateLargestCompleteSubtree(root);
          
              return max;
          }
          
          /**
           * Returns the largest complete subtree of a binary tree given by the input node
           *
           * @param root the root of the tree
           */
          public TreeInfo<TKeyType, TValueType> calculateLargestCompleteSubtree(INode<TKeyType, TValueType> root) {
              int size = 0;
          
              // a complete subtree must have the following attributes
              // 1. All leaves must be within 1 of each other.
              // 2. All leaves must be as far left as possible, i.e, L(child).count() > R(child).count()
              // 3. A complete subtree may have only one node (L child) or two nodes (L, R).
          
              if (root == null)
              {
                  return new TreeInfo<>(true, 0);
              }
              else if (!root.hasLeftChild() && !root.hasRightChild())
              {
                  return new TreeInfo<>(true, 1);
              }
          
              // have children
              TreeInfo<TKeyType, TValueType> leftInfo = calculateLargestCompleteSubtree(root.getLeft());
              TreeInfo<TKeyType, TValueType> rightInfo = calculateLargestCompleteSubtree(root.getRight());
          
              // case 1: not a complete tree.
              if (!leftInfo.isComplete || !rightInfo.isComplete)
              {
                  // Only one subtree is complete. Set it as the max and return false.
                  if(leftInfo.isComplete) {
                      max = Math.max(max, leftInfo.size);
                  }
                  else if(rightInfo.isComplete)
                  {
                      max = Math.max(max, rightInfo.size);
                  }
          
                  return new TreeInfo<>(false, -1);
              }
          
              // case 2: both subtrees complete
              int delta = Math.abs(leftInfo.size - rightInfo.size);
              if (delta <= 1)
              {
                  // both are complete but R could be 1 greater...use L tree.
                  size = leftInfo.size + 1;
                  max = Math.max(max, size);
                  return new TreeInfo<>(true, size);
              }
              else
              {
                  // limit to size of R + 1 if L - R > 1, otherwise L
                  if(leftInfo.size > rightInfo.size)
                  {
                      max = Math.max(max, leftInfo.size);
                      size = rightInfo.size + 1;
                  }
                  else
                  {
                      max = Math.max(max, rightInfo.size);
                      size = leftInfo.size;
                  }
          
                  return new TreeInfo<>(true, size + 1);
              }
          }
          

          【讨论】:

            【解决方案8】:

            python 中的一种简单递归方法。我已经测试了几棵树,到目前为止工作。

            # return (is_complete, max_height_so_far, is_perfect)
            def is_complete_tree(node):
                # null
                if not node:
                    return (True, -1, True)
            
                left_subtree = is_complete_tree(node.left_child)
                right_subtree = is_complete_tree(node.right_child)
            
                # if any of subtrees isn't complete, current tree is not complete
                if not left_subtree[0] or not right_subtree[0]:
                    return (False, max(left_subtree[1], right_subtree[1]), False)
            
                # if both subtrees are complete, there are 2 cases in order for current tree to be complete
                # case 1: subtrees with same height
                # left subtree must be perfect
                if left_subtree[1] == right_subtree[1] and left_subtree[2]:
                    return (True, left_subtree[1] + 1, right_subtree[2])
            
                # case 2: left subtree taller by 1
                # right subtree must be perfect
                if left_subtree[1] == right_subtree[1] + 1 and right_subtree[2]:
                    return (True, left_subtree[1] + 1, False)
            
                # otherwise not complete
                return (False, max(left_subtree[1], right_subtree[1]), False)
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2019-07-02
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-01-08
              • 2013-01-23
              • 2021-01-31
              • 1970-01-01
              相关资源
              最近更新 更多