zoukankan      html  css  js  c++  java
  • [LeetCode] 95. Unique Binary Search Trees II 独一无二的二叉搜索树之二

    CategoryDifficultyLikesDislikes
    algorithms Medium (62.81%) 407 -

    TagsCompanies

    给定一个整数 n,生成所有由 1 ... n 为节点所组成的 二叉搜索树 

    示例:输入:3输出:

    [
      [1,null,3,2],
      [3,2,null,1],
      [3,1,null,null,2],
      [2,1,3],
      [1,null,2,null,3]
    ]
    解释:
    以上的输出对应以下 5 种不同结构的二叉搜索树:
    
       1         3     3      2      1
               /     /      /       
         3     2     1      1   3      2
        /     /                        
       2     1         2                 3

    这道题是之前的 96 Unique Binary Search Trees 的延伸,之前那个只要求算出所有不同的二叉搜索树的个数,这道题让把那些二叉树都建立出来。
    解法一:递归
    这种建树问题一般来说都是用递归来解,这道题也不例外,划分左右子树,递归构造。
    查找二叉树的性质:左子树的所有值小于根节点,右子树的所有值大于根节点。

    所以如果求 1...n 的所有可能。

    我们只需要把 1 作为根节点,[ ] 空作为左子树,[ 2 ... n ] 的所有可能作为右子树。

    2 作为根节点,[ 1 ] 作为左子树,[ 3...n ] 的所有可能作为右子树。

    3 作为根节点,[ 1 2 ] 的所有可能作为左子树,[ 4 ... n ] 的所有可能作为右子树,然后左子树和右子树两两组合。

    4 作为根节点,[ 1 2 3 ] 的所有可能作为左子树,[ 5 ... n ] 的所有可能作为右子树,然后左子树和右子树两两组合。

    ...

    n 作为根节点,[ 1... n ] 的所有可能作为左子树,[ ] 作为右子树。

    至于,[ 2 ... n ] 的所有可能、 [ 4 ... n ] 以及其他情况的所有可能,可以利用上边的方法,把每个数字作为根节点,然后把所有可能的左子树和右子树组合起来即可。

    如果只有一个数字,那么所有可能就是一种情况,把该数字作为一棵树。而如果是 0,那就返回 null。

     1 class Solution {
     2 public:
     3     vector<TreeNode*> generateTrees(int n)
     4     {
     5         vector<TreeNode*> ans;
     6         if(n==0) return ans;
     7         return getAns(1,n);
     8     }
     9 private:
    10     vector<TreeNode*> getAns(int start,int end)
    11     {
    12         vector<TreeNode*> ans;
    13         if(start>end)
    14         {
    15             ans.push_back(nullptr);
    16         }
    17         else if(start == end)
    18         {
    19             TreeNode* tree = new TreeNode(start);
    20             ans.push_back(tree);
    21         }
    22         else
    23         {
    24             for(int i=start; i<=end; ++i)//依次把每个数字作为根
    25             {
    26                 vector<TreeNode*> leftTrees = getAns(start,i-1);//递归求i左子树的结果
    27                 vector<TreeNode*> rightTrees = getAns(i+1,end);////递归求i右子树的结果
    28                 //把i的所有可能的左子树和所有可能的右子树组合起来
    29                 for(int l = 0;l<leftTrees.size();++l)
    30                    for(int r = 0;r<rightTrees.size();++r)
    31                    {   
    32                        TreeNode* root = new TreeNode(i);
    33                        root->left = leftTrees[l];
    34                        root->right = rightTrees[r];
    35                        ans.push_back(root);
    36                    }
    37             }
    38         }
    39         return ans;
    40     }  
    41 };

    解法二:动态规划

        

    考虑 [] 的所有解
          null

    考虑 [ 1 ] 的所有解
           1

    考虑 [ 1 2 ] 的所有解
             2
           /
         1

              1
               
                2

    考虑 [ 1 2 3 ] 的所有解
                3
              /
            2
          /
        1

               2
             /
            1    3

             3
           /
          1
           
             2

                 1
                 
                    3
                   /
                 2

          1
           
            2
             
                3

         仔细分析,可以发现一个规律。首先我们每次新增加的数字大于之前的所有数字,所以新增加的数字出现的位置只可能是根节点或者是根节点的右孩子,

    右孩子的右孩子,右孩子的右孩子的右孩子等等,总之一定是右边。其次,新数字所在位置原来的整个子树,改为当前插入数字的左孩子即可,因为插入数字是最大的。

        比如对下面的1,2,3,4组成的二叉搜索树增加节点5;

            1
             
                3
              /   
             2     4

    第一步:作为根结点,原来的整棵树作为5的左子树

                   5
                 /   
               1
               
                  3
                /   
               2     4

    第二步:作为1的右节点,1的原本的右子树作为5的左子树:

            1
             
                5
              / 

            3
          /   
        2      4         

     第三步:作为3的右节点,3的原本的右子树作为5的左子树:

              1
               
                3
              /   
             2      5
                  /   
                4 

     第四步:作为4的右节点,的原本的右子树(null)作为5的左子树:

             1
             
                3
              /   
             2     4  
                        
                     5 

    动态规划实现的代码如下:

     1 /*
     2  * @Descripttion: 
     3  * @version: 
     4  * @Author: wangxf
     5  * @Date: 2020-06-07 00:03:19
     6  * @LastEditors: Do not edit
     7  * @LastEditTime: 2020-06-09 01:53:31
     8  */ 
     9 /*
    10  * @lc app=leetcode.cn id=95 lang=cpp
    11  *
    12  * [95] 不同的二叉搜索树 II
    13  */
    14 
    15 // @lc code=start
    16 /**
    17  * Definition for a binary tree node.
    18  * struct TreeNode {
    19  *     int val;
    20  *     TreeNode *left;
    21  *     TreeNode *right;
    22  *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
    23  *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    24  *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
    25  * };
    26  */
    27 
    28 class Solution {
    29 public:
    30     vector<TreeNode*> generateTrees(int n)
    31     {
    32         vector<TreeNode*> pre;
    33         if(n==0)
    34         {
    35             return pre;
    36         }
    37         pre.push_back(nullptr);
    38         for(int i=1;i<=n;++i)
    39         {
    40             vector<TreeNode*> cur;
    41             for(auto root:pre)
    42             {
    43                 //新节点作为根插入
    44                 TreeNode* insert = new TreeNode(i);
    45                 insert->left = root;
    46                 cur.push_back(insert);
    47                 //新节点作为根的右节点,右节点的右节点,右节点的右节点的右节点...插入
    48                 for(int j=0;j<n;++j)//最多有i-1个右节点
    49                 {
    50                      TreeNode* new_root = copytree(root);//拷贝生成新的待插入树
    51                      TreeNode* right = new_root;//工作指针
    52                      int k = 0;
    53                      for(;k<j;++k)
    54                      {
    55                         if(right==nullptr) break; 
    56                         right = right->right;
    57                      }
    58                      if(right==nullptr){
    59                          break;
    60                      }
    61                      insert = new TreeNode(i);
    62                      insert->left = right->right;
    63                      right->right = insert;
    64                      cur.push_back(new_root);
    65                 }    
    66             }
    67             pre = cur;
    68         }
    69         return pre;
    70     }
    71 private:
    72     //拷贝生成一棵树
    73     TreeNode* copytree(TreeNode* root)
    74     {
    75         if(!root)
    76         {
    77              return nullptr;
    78         }
    79         TreeNode* new_root = new TreeNode(root->val);
    80         new_root->left = copytree(root->left);
    81         new_root->right = copytree(root->right);
    82         return new_root;
    83     }
    84     
    85 };
    86 // @lc code=end

    注:代码中存在内存泄漏的风险,不宜用在生产环境中。

  • 相关阅读:
    jvm 垃圾收集器
    MySQL 查询结果去除边框
    MySQL5.7 半同步复制技术
    MySQL 5.7半同步复制技术 zero
    redis 迁移工具 redisshake
    MySQL 如何找出占用CPU较高的SQL
    部署redis sentinel
    MySQL的SQL_CALC_FOUND_ROWS 类似count(*)
    MongoDB 副本集删除超级用户后恢复
    【Linux】关于 Systemd/Journal
  • 原文地址:https://www.cnblogs.com/wangxf2019/p/13070058.html
Copyright © 2011-2022 走看看