前言
开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情
参考链接:东哥带你刷二叉搜索树(基操篇) :: labuladong的算法小抄 (gitee.io)
题目
题目一
题解:
题目要让我们判断,这个树是不是一个搜索树
那么我们可以回忆下,二叉搜索树的性质
- 二叉搜索树,左小右大,中序遍历,应该是单调增
- 当前节点,一定小于右子树的所有节点
但是我们在二叉树遍历的时候,我们只能检查他的左右两个孩子,是不是满足,左小右大原则,但是无法对所有的右边孩子都进行判断
这时候我们就需要引入两个变量max和min
如果他是一个合格的搜索树
那么对于当前节点而言
- 左子树的所有节点值应该都是小于当前节点值
- 右子树的所有节点值应该都是大于当前节点值
所以我们需要重新写一个函数,然后传入三个参数
代码:
/**
* Definition for a binary tree node.
* class TreeNode {
* val: number
* left: TreeNode | null
* right: TreeNode | null
* constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
* }
*/
function isValidBST(root: TreeNode | null): boolean {
// 首先必须明确的是,搜索树保证,左小右大
// 但是,搜索树其实保证,左边树的所有节点都要比右边树的所有节点小
// if(root === null){
// return true
// }
// if(root.left && root.left.val > root.val){
// return false
// }
// if(root.right && root.right.val < root.val){
// return false
// }
// return isValidBST(root.left) && isValidBST(root.right)
return IsValidBST(root,null,null)
};
// 因为我们没法将当前节点和后面的右边子树连接起来
// max.val > root.val > min.val
const IsValidBST = (root:TreeNode,min:TreeNode,max:TreeNode)=>{
//
if(root === null){
return true
}
// 当min不为空,说明,右子树
if(min && min.val >= root.val){
return false
}
// 当max不为空,说明,有最大值,左子树
if(max && max.val <= root.val){
return false
}
// 对于当前节点来说
// 左子树的所有节点,应该是要小于这个当前节点的,所以当前节点就是MAX
// 对于右子树来说,我当前的节点,应该是比所有的右边的节点都要小的,所以,我们将他设置为最小值
return IsValidBST(root.left,min,root)&& IsValidBST(root.right,root,max)
}
题目二(查)
链接:700. 二叉搜索树中的搜索 - 力扣(LeetCode)
题解:
- 正如上面介绍的,我们知道,BST的特性是左小右大
- 那么我们可以利用这一特性,进行类似二分搜索的方式
- 假如
root.val < val,那么就说明,这个值太小了,就去右子树上面寻找 - 假如
root.val > val,那么就说明,这个值偏大,那么就去左子树上寻找
代码:
/**
* Definition for a binary tree node.
* class TreeNode {
* val: number
* left: TreeNode | null
* right: TreeNode | null
* constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
* }
*/
function searchBST(root: TreeNode | null, val: number): TreeNode | null {
// 找到根节点等于val的值,然后返回
// 首先,必须知道,对于二叉搜索树,他的中序遍历一定是单调增的
// 所以我们只要在中序遍历的时候加上二分搜索就可以得到答案
if(root === null){
return null;
}
if(root.val <val){
// 这个值比较大,那么就要去他的右子树上寻找
return searchBST(root.right,val)
}
if(root.val> val){
return searchBST(root.left,val)
}
if(root.val === val){
return root
}
};
但是在第一次操作的时候,我进行了一次判断
if(root.val <val){
// 这个值比较大,那么就要去他的右子树上寻找
if(root.right)
return searchBST(root.right,val)
}
但是这样的结果,就是假如没有root.val === val的值的时候,他就会出现undefined
后来发现,其实这样做,是完全没有必要的
因为我有一个兜底的if(root){return null}
所以,其实我们没有必要去多判断他是不是满足不为null这么回事
题目三(增)
链接:701. 二叉搜索树中的插入操作 - 力扣(LeetCode)
题解:
- 使用二分法,去往里面插入元素
- 但是这次不是简单的
return就结束了 - 而是需要将树的结构进行变化
代码:
/**
* Definition for a binary tree node.
* class TreeNode {
* val: number
* left: TreeNode | null
* right: TreeNode | null
* constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
* }
*/
function insertIntoBST(root: TreeNode | null, val: number): TreeNode | null {
// 现在需要往二叉搜索树里面插入一个数
// 依然保证,树的结构成立
if(root === null){
return new TreeNode(val)
}
// 因为题目说了,不存在,值相同的情况
// 因为需要将树进行改变,所以,不能简单的return就结束了
if(root.val < val){
// 去右子树
root.right = insertIntoBST(root.right,val)
}
if(root.val > val){
root.left = insertIntoBST(root.left,val)
}
// 最后返回这个新的树
return root
};
第三题(删)
链接:450. 删除二叉搜索树中的节点 - 力扣(LeetCode)
题解:
-
要想删除,第一步肯定是,先找到这个元素
-
然后对这个元素进行一些修改
-
假如他是一个叶子节点,直接断掉就行
-
假如他没有左或右节点,我们只需将,现有的节点,放到需要删除的地方就行了
- 也就是直接返回,有的那个子节点就行
-
但是假如,他的左右子节点都齐全,那么就会复杂一点
-
因为需要继续满足
BST的条件,那么也就意味着,我们删除的那个节点- 要么是左子树的最大节点
- 要么是右子树的最小节点
-
这里,我们找的是,右子树的最小节点
-
其实还是比较好理解的,
BST而言,最小节点一定在:左子树的叶子节点,那么只需要找到他,然后将他交给根节点,然后删除这个最小节点就可以了
-
代码:
/**
* Definition for a binary tree node.
* class TreeNode {
* val: number
* left: TreeNode | null
* right: TreeNode | null
* constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
* }
*/
function deleteNode(root:TreeNode|null,key:number):TreeNode | null {
if(root === null){
return null
}
if(root.val < key){
root.right = deleteNode(root.right,key)
}
if(root.val > key){
root.left = deleteNode(root.left,key)
}
if(root.val === key){
// return root
// 那么进行删除
// 假如左或右子树缺少,那么只要将有的那个子树的值放上去就好了
// 同时也排除了,叶子节点情况
if(root.left === null){
return root.right
}
if(root.right === null){
return root.left
}
// 假如左右子树都很齐全的情况,那么我就要去找右子树里面最小的那个值放进来,或者左子树里面最大的那个放进来
if(root.left !== null && root.right !== null){
// 那么我们就去寻找,右子树里面的最小值
let minNode = GetMin(root.right);
// 让当前最小节点作为根节点,并且删除那个小的节点
root.val = minNode.val
// 然后删除MinNode
root.right = deleteNode(root.right,minNode.val)
}
}
return root
}
const GetMin = (root:TreeNode)=>{
// 对于BST而言,最小的节点,一定是,左子树的那个叶子节点
while(root.left !== null){
root = root.left
}
return root
}
总结
从这几道基本题看出来,其实在做搜索树的题目前,你需要知道
BST的左小右大,这个可以使用二分查找的方法BST的中序遍历是递增的
对树的操作无非遍历 + 访问,遍历就是「找」,访问就是「改」。
对于改而言,其实就要函数返回
TreeNode类型,并要对递归调用的返回值进行接受