手把手带你刷通二叉搜索树(第三期)——如何计算所有合法 BST
96. 不同的二叉搜索树 (中等)fail
给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
什么方式能够正确地穷举合法 BST 的数量呢?
所以如果固定3作为根节点,左子树节点就是{1,2}的组合,右子树就是{4,5}的组合。
左子树的组合数和右子树的组合数乘积就是3作为根节点时的 BST 个数。
函数的含义为:计算闭区间 [1, n] 组成的 BST 个数。当拆解这个区间时,肯定存在重叠子问题。
因为二叉搜索树起的二分作用,所以每次拆解后的两边区间都是连续的
let dp = null;
var numTrees = function(n) {
dp = new Array(n+1).fill(0).map(val => new Array(n+1).fill(0))
return count(1,n)
};
function count(st, en) {
if(st >= en) return 1;
// 查备忘录
if(dp[st][en]) return dp[st][en]
// 当前根节点的众数
let res = 0;
for(let i=st; i<=en; i++){
let left = count(st, i-1)
let right = count(i+1, en)
res += (left * right)
}
// 将结果存入备忘录
dp[st][en] = res;
return res;
}
95. 不同的二叉搜索树 II (中等)
给你一个整数 n ,请你生成并返回所有由 n 个节点组成且节点值从 1 到 n 互不相同的不同 二叉搜索树 。可以按 任意顺序 返回答案。
思路与上一题一样:
1、穷举root节点的所有可能。
2、递归构造出左右子树的所有合法 BST。
3、给root节点穷举所有左右子树的组合。
var generateTrees = function(n) {
return build(1, n)
};
function build(st, en) {
// 返回的是列表
if(st>en) return [null]
if(st==en) return [new TreeNode(st)]
let res = []
for(let i=st; i<=en; i++){
let lefts = build(st, i-1)
let rights = build(i+1, en)
for(left of lefts) {
for(right of rights) {
let root = new TreeNode(i)
root.left = left
root.right = right
res.push(root)
}
}
}
return res;
}
1373. 二叉搜索子树的最大键值和 (困难)
如果当前节点要做的事情需要通过左右子树的计算结果推导出来,就要用到后序遍历。
给你一棵以 root 为根的 二叉树 ,请你返回 任意 二叉搜索子树的最大键值和。
注意:
最大的二叉树不一定是二叉搜索树
虽然节点4的值最大,但不能把它单独看
最核心的思路是明确当前节点需要做的事情是什么。——最终目的是计算BST 的最大和。
如果前序遍历,就要使用三个递归辅助函数,输入是当前根节点,然后获取上面三点的信息。
以 root 为根的二叉树不是 BST,那他的父亲(往上的节点)也不是BST
根节点要大于左子树的最大值,小于右子树的最小值
而根节点的最小值在左子树找,最大值在右子树找
这两个地方要分清
let maxsum = 0
var maxSumBST = function(root) {
maxsum = 0
traverse(root)
return maxsum
};
// 返回值 []是否为BST, 最小值, 最大值, 节点值之和]
function traverse(root) {
if(root == null) return [true, null, null, 0]
let left = traverse(root.left)
let right = traverse(root.right)
// 后序遍历操作
let res = new Array(4)
// 判断以 root 为根的二叉树是不是 BST
if(left[0] && right[0] && (left[2]==null || left[2]<root.val)
&& (right[1]==null || right[1]>root.val)) {
res[0] = true
// 计算以 root 为根的这棵 BST 的最小值
res[1] = left[1]==null? root.val : Math.min(left[1], root.val)
// 计算以 root 为根的这棵 BST 的最大值
res[2] = right[2]==null? root.val : Math.max(right[2], root.val)
// 计算以 root 为根的这棵 BST 所有节点之和
res[3] = left[3] + right[3] + root.val
// 更新全局变量
maxsum = Math.max(maxsum, res[3])
} else {
res[0] = false
}
return res
}
501. 二叉搜索树中的众数 (简单) fail
清空数组元素
与有序数组中查找众数一样,需要一些变量来获取信息
var findMode = function(root) {
let maxcount = 1, curcount = 1, pre = null;
let res = []
function traverse(root) {
if(root == null) return
traverse(root.left)
// 中序遍历操作
if(pre == null) { // 处理第一个节点
res.push(root.val)
} else {
if(root.val == pre.val) {
curcount++
if(curcount == maxcount){
res.push(root.val)
} else if(curcount > maxcount){
res = []
maxcount = curcount
res.push(root.val)
}
} else {
curcount = 1
if(curcount == maxcount) res.push(root.val)
}
}
// 记录上一个节点
pre = root
traverse(root.right)
}
traverse(root)
return res
};
530.783. 二叉搜索树的最小绝对差 (简单)
给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。
上一题的弟弟
var getMinimumDifference = function(root) {
let res = Number.MAX_SAFE_INTEGER, pre = null
function traverse(root) {
if(root == null) return
traverse(root.left)
if(pre) {
res = Math.min(res, (root.val-pre.val))
}
pre = root
traverse(root.right)
}
traverse(root)
return res
};
669. 修剪二叉搜索树 (中等)
独立分析:
- 当前root不在区间内:(根节点不能用)
1.如果小于区间,看右子树;2.如果大于区间,看左子树
- 当前root在区间内:(先等左右子树出结果——后序遍历)
root左子树的最大值小于区间, 剪掉
root右子树的最小值大于区间,剪掉
函数的定义:返回合法的子树——先序遍历
var trimBST = function(root, low, high) {
if(root == null) return null
if(root.val < low) {
return trimBST(root.right, low, high)
} else if(root.val > high) {
return trimBST(root.left, low, high)
}
root.left = trimBST(root.left, low, high)
root.right = trimBST(root.right, low, high)
return root
};
最后学一下:递归改迭代,因为数据结构也经常问怎么将中后序遍历使用迭代的方式。
文章认为是因为:面试越来越卷,他的观点:二叉树的题目还是用递归的方式来做,因为递归是最符合二叉树结构特点的。
二叉树八股文:递归改迭代
这篇没有放相关的题目,重点是理解怎么迭代,最直接题目就是二叉树的遍历。下面两幅图是迭代的核心。
不管是什么遍历,节点入栈的顺序都是一样的
虽然这框架通用性好,但有点难理解和为啥他满足各种情况
145. 二叉树的后序遍历
94. 二叉树的中序遍历

function pushleft(root) {
while(root) {
stack.push(root)
root = root.left
}
}
var postorderTraversal = function(root) {
stack = []
let visited = new TreeNode(-1), res = []
pushleft(root)
while(stack.length) {
let cur = stack[stack.length-1]
// p 的左子树被遍历完了,且右子树没有被遍历过——现在到根节点
if((cur.left==null || cur.left==visited) && cur.right!=visited) {
pushleft(cur.right)
}
// p 的右子树被遍历完了
if(cur.right==null || cur.right==visited){
// 后序遍历:等右子树根节点遍历完才到自己
res.push(cur.val)
visited = stack.pop()
}
}
return res;
};
中序遍历
var inorderTraversal = function(root) {
stack = []
let visited = new TreeNode(-1), res = []
pushleft(root)
while(stack.length) {
let cur = stack[stack.length-1]
// p 的左子树被遍历完了,且右子树没有被遍历过——现在到根节点
if((cur.left==null || cur.left==visited) && cur.right!=visited) {
// 中序遍历:自己先读再去找右子树
res.push(cur.val)
// 去遍历 p 的右子树
pushleft(cur.right)
}
// p 的右子树被遍历完了
if(cur.right==null || cur.right==visited){
// 后序遍历:等右子树根节点遍历完才到自己
// res.push(cur.val)
// 以 p 为根的子树被遍历完了,出栈
visited = stack.pop()
}
}
return res;
};