专题:二叉搜索树

什么事二叉搜索数

leetcode98. 验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

节点的左子树只包含小于当前节点的数。
   节点的右子树只包含大于当前节点的数。
   所有左子树和右子树自身必须也是二叉搜索树。

示例 1:

输入:
   2
  /
 1   3
输出: true

示例 2:

输入:
   5
  /
 1   4
    /
   3   6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
    根节点的值为 5 ,但是其右子节点值为 4 。

这道题么考频不是很高,但诠释了二叉搜索树的概念,掌握起来也是很快的,是二叉搜索树的基础题
做完了就知道二叉搜索树大概是个什么东西

python通过代码

class Solution:
    def isValidBST(self, root: TreeNode) -> bool:
        return self.helper(root,None,None)
    def helper(self,root,low,high):
        if not root: return True
        if low != None and root.val <= low: return False
        if high != None and root.val >= high: return False
        if not self.helper(root.left,low,root.val): return False
        if not self.helper(root.right,root.val,high): return False
        return True

规模为n的二叉搜索树一共有多少种?

leetcode96. 不同的二叉搜索树
95,96是二叉树标签下考频最高的两道题,尤其重要,必须牢固掌握
给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?

示例:

输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:

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

这道题用dp去做,
状态转移方程看东北程序员视频里的推导
图片.png
这个递推式有必要记下来,因为这是卡特兰数的递推式
在leetcode中碰到的组合题,前几项n=1,2,3,4的时候 结果数列为1,2,5,14,...
看到1,2,5,14就要敏感,这个东西基本就是卡特兰数了,因为他不会给你太弱智的找规律
那知道这个是卡特兰数之后,问题就迎刃而解了

class Solution:
    def numTrees(self, n: int) -> int:
        dp = [1] * (n+1)
        for i in range(1,n+1):
            sum_ = 0
            for j in range(i):
                sum_ += dp[j]*dp[i-j-1]
            dp[i] = sum_
        return dp[-1]

总结 卡特兰数记两个东西
1.通项公式(也就是题目dp中的状态转移方程)
2.前四项1,2,5,14
周赛如果遇到了,直接编,想都不要想,碰运气,编不出来拉倒
如果不放心,基本也可以顺着卡特兰数通项的思路去想前后两个数之间的关系,逻辑理顺了就能确保是卡特兰数了

95. 不同的二叉搜索树 II

这道题比96更难一点,相比96他要生成所有的二叉树,但可以按96如法炮制,用很复杂的dp去做(嵌套了四层for循环),实际上时间么在可接受范围之内,卡的不是很严
图片.png
先看题目吧

给定一个整数 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再来做95 思路是很明晰的

class Solution:
    def generateTrees(self, n: int) -> List[TreeNode]:
        dp = [[None]] * (n+1)
        if not n: return []
        for i in range(1,n+1):
            dp[i] = []
            for j in range(i):
                for lefttree in dp[j]:
                    for righttree in dp[i-j-1]:
                        thistree = TreeNode(j+1)
                        thistree.left = lefttree
                        thistree.right = self.copytree(righttree,j+1)
                        dp[i].append(thistree)
        return dp[-1]
    def copytree(self,root,k):
        if not root: return None
        res = TreeNode(root.val+k)
        res.left = self.copytree(root.left,k)
        res.right = self.copytree(root.right,k)
        return res

需要注意的点是,左子树可以直接引用传参,但右子树需要给每个节点加上数值
比如1234567 取j=3

你把4作为root,左子树递归调用dp[3]
右子树也调用dp[3],但出来的子树是123,而你需要的右子树范围是567
所以这里每个节点的数值加上j+1(3+1=4)进行深拷贝
额外实现了一个深拷贝+更改节点数值的函数在下面
这个copy函数很简单了,二叉树遍历法的常规操作
这道题不止这一个方法,还有一个方法请看东北程序员
https://www.bilibili.com/video/av68783564?p=93
第二种写法就是DFS+递归,也许面试官问到更想听这种方法,比第一种代码简洁很多

class Solution:
    def generateTrees(self, n: int) -> List[TreeNode]:
        if not n: return []
        return self.helper(1,n)
    def helper(self,i,j):
        if j < i: return [None]
        res = []
        for idx in range(i,j+1):
            left = self.helper(i,idx-1)
            right = self.helper(idx+1,j)
            for lefttree in left:
                for righttree in right:
                    thistree = TreeNode(idx)
                    thistree.left = lefttree
                    thistree.right = righttree
                    res.append(thistree)
        return res

一个坑:这里我建议把helper的i,j换成start,end或者left,right. 因为我一开始把其中一个i打成1了,报错找了很久才找到,由此可见命名规范的重要性
时间也比上一种少
执行结果:
通过
显示详情
执行用时 :52 ms, 在所有 python3 提交中击败了96.03% 的用户
内存消耗 :14 MB, 在所有 python3 提交中击败了95.19%的用户

99. 恢复二叉搜索树

二叉搜索树中的两个节点被错误地交换。

请在不改变其结构的情况下,恢复这棵树。

示例 1:

输入: [1,3,null,null,2]

1
 /
3
 
  2

输出: [3,1,null,null,2]

3
 /
1
 
  2

示例 2:

输入: [3,1,4,null,null,2]

3
/
1   4
  /
 2

输出: [2,1,4,null,null,3]

2
/
1   4
  /
 3

进阶:

使用 O(n) 空间复杂度的解法很容易实现。
   你能想出一个只使用常数空间的解决方案吗?

其实就遍历一遍,用全局变量记录,因为函数递归调用起来记录变量顺序很麻烦,必须用全局变量脱离递归套用的约束,省去传参的麻烦

class Solution:

    def recoverTree(self, root: TreeNode) -> None:
        """
        Do not return anything, modify root in-place instead.
        """
        self.err1 = None
        self.err2 = None
        self.pre = None
        def helper(root):
            if not root: return
            helper(root.left)
            if self.pre and self.pre.val > root.val and not self.err1:
                self.err1 = self.pre
            if self.err1 and self.pre.val > root.val:
                self.err2 = root
            self.pre = root
            helper(root.right)
        helper(root)
        self.err1.val,self.err2.val = self.err2.val,self.err1.val

这里python用的是类的属性变量来模拟全局变量(因为函数内部也可以直接作用类的属性变量)
凡是可以递归的东西必然可以迭代,而这题使用迭代就不需要全局变量的概念了

原文地址:https://www.cnblogs.com/Akarinnnn/p/12091345.html