今天是LeetCode专题第61篇文章,我们一起来看的是LeetCode95题,Unique Binary Search Trees II(不同的二叉搜索树II)。

这道题的官方难度是Medium,点赞2298,反对160,通过率40.5%。我也仿照steam当中游戏评论的分级,给LeetCode中的题目也给出一个评级标准。按照这个点赞和反对的比例,这道题可以评到特别好评。从题目内容上来说,这是一道不可多得基础拷问的算法题,看着不简单,做起来也不简单,但看了题解之后,你会发现也没你想象得那么难。

题意

给定一个n,表示1到n这n个数字,要求用这n个数构建二叉搜索树(Binary Search Tree)简称BST,要求我们构建出所有不同的二叉搜索树。

样例

Input: 3
Output:
[
  [1,null,3,2],
  [3,2,null,1],
  [3,1,null,null,2],
  [2,1,3],
  [1,null,2,null,3]
]
Explanation:
The above output corresponds to the 5 unique BST's shown below:

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3

从这个case当中我们可以看到,当n=3的时候,二叉搜索树一共有5中不同的情况。为了方便展示,Output当中展示的内容是这些树中序遍历的结果。但实际上我们要返回的是树根节点构成的list。

哦哦,对了题目当中还有一个n <= 8的条件,所以如果你是一个狼人的话,也可以把所有的情况都手动实现。

解法

这道题我感觉官方难得给的有点低了,应该可以算得上是Hard了。拿到手我们思路没多少,但是发现的问题却一大堆。比如说我们怎么构建这些BST,并且怎么判断两颗BST是否重复呢?难道要整个遍历一遍之后,一个节点一个节点地判断是否相同吗?显然这也太耗时了,而且编码也不容易。举个例子[2, 1, 3]和[2, 3, 1]生成的BST是一样的,这种情况很难解决。

即使我们解决了这个问题,那么又怎么样保证我们可以顺利找到所有的答案,而不会有所遗漏呢?这两个核心的问题很难回答,并且你会发现越想越复杂。

这个有点像什么呢?就好像是古代行军打仗,攻打一个异常坚固的堡垒,正面攻坚可能非常困难,我们想出来的办法都在敌人的预料之中,总能找到破解之道。这个时候就需要我们有敏锐的意识,就好像是一个经验丰富的老将,观察地形之后发现强攻不可为,那么自然就会退下来想一想其他的办法。

我们做题也是一样,正面硬刚做不出来,再耗下去也不会有好办法,往往就需要出奇制胜了。

我们试着把问题缩小,化整为零,如果n=1,那么很简单,BST就只有一种,这个是我们都知道的东西。如果n=2呢,也不难,只有两种,无非是[1, 2]和[2, 1]。这时候我们停住,来思考一个问题,n=2的情况和n=1的情况有什么区别呢?

如果仔细想,会发现其实没什么区别, 我们只不过是在n=1的情况当中加入了2这个数字而已。同理,我们发散一下n=k和n=k+1的时候生成的BST之间有什么关系呢?如果我们知道了n=k时候的所有BST,可不可以利用这个关系生成n=k+1时的所有结果呢?

当然是可以的,实际上这也是一个很好的做法。这是一种最基本的二叉树,假设我们要往其中插入一个最大的节点,我们很容易发现,一共有三种方法。


image-20200809155208137

第一种方法将原搜索树作为新节点的左孩子节点。

第二种方法是将新的节点插入根节点的右侧,代替根节点的右孩子。由于这个新加入的节点大于其他所有节点,所以根节点的右孩子会变成它的左孩子。

最后一种方法就是将它变成叶子节点,也就是放在最底层。

我们稍加思考就可以想明白,如果要把一个最大的元素插入BST,那么它只能够放在最右侧的树分支上。也就是说当我们从n=k的情况推导k+1时,根据最右侧链路长度的不同,会衍生出多个解来。只要抓住了这一点,这其中的递推关系就很明显了。

我们用代码来实现这个想法,思路虽然简单,但是实现起来要复杂一些,有很多细节需要考虑。我在这里不一一列举了,大家查看代码当中的注释吧。

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def generateTrees(self, n: int) -> List[TreeNode]:
        ret = []
        
        # 拷贝二叉树
        def copyTree(node):
            if node is None:
                return None
            u = TreeNode(node.val)
            u.left = copyTree(node.left)
            u.right = copyTree(node.right)
            return u
        
        def dfs(n):
            # n=1只有一种情况
            if n == 1:
                ret.append(TreeNode(1))     
                return
            
            dfs(n-1)
            # 遍历n=k时的所有情况
            for i in range(len(ret)):
                u = ret[i]
                node = TreeNode(n)
                node.left = u
                ret[i] = node
                
                it = u
                rank = 0
                # 将n插入最右侧链路当中,有几种可以选择的位置,就会诞生几种新的放法
                while it is not None:
                    node = TreeNode(n)
                    # 为了防止答案之间互不影响,所以需要把树拷贝一份
                    new = copyTree(u)
                    cur = new
                    
                    # rank记录的是每一个解对应的n放入的深度
                    for _ in range(rank):
                        cur = cur.right
                    
                    node.left = cur.right
                    cur.right = node
                    
                    ret.append(new)
                    
                    it = it.right
                    rank += 1
            
        if n == 0:
            return ret
        dfs(n)
        return ret

这种方法当然是可行的, 我们也成功做了出来。但是它也有很多问题,最大的问题就是细节太多,而且处理起来太麻烦了。那么有没有简单一点的方法呢?

我们来思考一个问题,我们通过递推和迭代从n=k构造出了n=k+1的情况,这一种构造和递推的思路非常巧妙。但问题是,我们构造和递推的方法难道只有这一种吗?能不能想出其他简便一些的构造和递推的方法呢?

既然我这么说,那么很显然,它是可以的,怎么做呢?

这要用到BST另外一个性质,我们都知道对于BST来说,它有一个性质是对于根节点来说,所有比它小的元素都出现在它的左侧,比它大的元素都在它的右侧。那么假如我们知道根节点是i,那么我们可以确定1到i-1出现在它的左子树,i+1到n出现在它的右子树。假设说我们已经得到了左右子树的所有情况,我们只需要把它们两两组合在一起,是不是就得到了答案了呢?

我这么说你们理解起来可能还是会觉得有些费劲,但是一旦查看代码,你们一定会为这段逻辑的简易性而折服,看起来实在是太简明也太巧妙了。

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def generateTrees(self, n: int) -> List[TreeNode]:
        ret = []
        
        def dfs(l, r):
            cur = []
            if r < l:
                cur.append(None)
                return cur
            
            # 枚举作为树根的元素
            for i in range(l, r+1):
                # 枚举左右子树的所有子树的构成情况
                for u in dfs(l, i-1):
                    for v in dfs(i+1, r):
                        node = TreeNode(i)
                        node.left = u
                        node.right = v
                        cur.append(node)
            return cur 
            
        if n == 0:
            return ret
        return dfs(1, n)

和上面的方法一样,这也是递归和构造方法的结合,但显然无论从运行效率上还是代码的简易性上,这种做法都要好不少,实实在在地体现了代码和逻辑之美。

今天的文章到这里就结束了,如果喜欢本文的话,请来一波素质三连,给我一点支持吧(关注、转发、点赞)。

- END -

原文链接,求个关注

LeetCode 95 | 构造出所有二叉搜索树

LeetCode 95 | 构造出所有二叉搜索树的更多相关文章

  1. Leetcode:96. 不同的二叉搜索树

    Leetcode:96. 不同的二叉搜索树 Leetcode:96. 不同的二叉搜索树 题目在链接中,点进去看看吧! 先介绍一个名词:卡特兰数 卡特兰数 卡特兰数Cn满足以下递推关系: \[ C_{n ...

  2. C# leetcode 之 096 不同的二叉搜索树

    C# leetcode 之 096 不同的二叉搜索树 题目描述 给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种? 二叉搜索树定义 左子树上所有节点的值小于根节点, 右子树上左右 ...

  3. 95题--不同的二叉搜索树II(java、中等难度)

    题目描述:给定一个整数 n,生成所有由 1 ... n 为节点所组成的 二叉搜索树 . 示例如下: 分析:这一题需要对比LeetCode96题来分析:https://www.cnblogs.com/K ...

  4. [LeetCode] Serialize and Deserialize BST 二叉搜索树的序列化和去序列化

    Serialization is the process of converting a data structure or object into a sequence of bits so tha ...

  5. [LeetCode] Binary Search Tree Iterator 二叉搜索树迭代器

    Implement an iterator over a binary search tree (BST). Your iterator will be initialized with the ro ...

  6. LeetCode 109——有序链表转化二叉搜索树

    1. 题目 2. 解答 2.1. 方法一 在 LeetCode 108--将有序数组转化为二叉搜索树 中,我们已经实现了将有序数组转化为二叉搜索树.因此,这里,我们可以先遍历一遍链表,将节点的数据存入 ...

  7. LeetCode 109. 有序链表转换二叉搜索树(Convert Sorted List to Binary Search Tree)

    题目描述 给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树. 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1. 示例: 给定的有序链表: ...

  8. [LeetCode] 109. 有序链表转换二叉搜索树

    题目链接 : https://leetcode-cn.com/problems/convert-sorted-list-to-binary-search-tree/ 题目描述: 给定一个单链表,其中的 ...

  9. LeetCode 中级 - 有序链表转换二叉搜索树(109)

    给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树. 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1. 示例: 给定的有序链表: [-10 ...

随机推荐

  1. luogu P4852 yyf hates choukapai 单调队列优化dp

    LINK:yyf hates choukapai 由于赶时间 要做的题有点小多.. 所以简略概述一下. 容易想到一个暴力dp \(f_{i,j,k}\)表示到了第i张牌此时连抽j次已经单抽k次的最大值 ...

  2. Servlet容器启动过程

    参考:https://blog.csdn.net/fredaq/article/details/9366043 一.概念 所谓Servlet容器其实说白了是符合Servlet规范的Java web容器 ...

  3. Oracle APEX 发送邮件

    1.网络服务安全设置 Oracle 11gR2的版本,可能导致邮件发送失败(ORA-24247: network access denied by access control list (ACL)) ...

  4. springboot 使用DruidDataSource 数据源

    一.添加依赖 <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</art ...

  5. /usr/bin/docker-current: Error response from daemon: driver failed programming external connectivity on endpoint naughty_wozniak (444e26e0a2a3adb1ff88177ead86099ad64c0406afcec179ce7cfeef8ffa2d5c): (i

    /usr/bin/docker-current: Error response from daemon: driver failed programming external connectivity ...

  6. 改改Python代码,运行速度还能提升6万倍

    这份最新研究指出,在后摩尔定律时代,人类所获得的的算力提升将更大程度上来源于计算堆栈的「顶层」,即软件.算法和硬件架构,这将成为一个新的历史趋势. 很多人学习python,不知道从何学起.很多人学习p ...

  7. CSS概述(二)

    目录 CSS背景属性 设置背景颜色 background-color 设置背景图像 background-image 设置背景重复 background-repeat 设置背景定位 backgroun ...

  8. C#LeetCode刷题之#896-单调数列(Monotonic Array)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3760 访问. 如果数组是单调递增或单调递减的,那么它是单调的. ...

  9. LeetCode 309 Best Time to Buy and Sell Stock with Cooldown 解决方案

    题目描述 给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 .​ 设计一个算法计算出最大利润.在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票): 你不能同时参与多笔 ...

  10. 读取topic数据存储到文件内

    基于python3.6 from pykafka import KafkaClient import logging logging.basicConfig(level=logging.INFO) d ...