---
主题列表:juejin, github, smartblue, cyanosis, channing-cyan, fancy, hydrogen, condensed-night-purple, greenwillow, v-green, vue-pro, healer-readable, mk-cute, jzman, geek-black
贡献主题:github.com/xitu/juejin…
theme: juejin highlight:
概念
二叉搜索树(BST)是一种特殊的二叉树,它满足如下特性:
- 每个节点的值必须大于(或等于)其左子树中的任意值
- 每个节点的值必须小于(或等于)其右子树中的任意值
下面是一个二叉搜索树的示例:
验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
节点的左子树只包含小于当前节点的数。 节点的右子树只包含大于当前节点的数。 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:
2
/ \
1 3
输出: true
示例 2:
输入:
5
/ \
1 4
/ \
3 6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
根节点的值为 5 ,但是其右子节点值为 4 。
链接:leetcode-cn.com/leetbook/re… 来源:力扣(LeetCode)
递归
class Solution {
public boolean isValidBST(TreeNode root) {
return dfs(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
public boolean dfs(TreeNode root, long lower, long upper) {
if (root == null) return true;
// 根节点 <= 下边界或 >= 上边界
if (root.val <= lower || root.val >= upper) return false;
// 递归判断左子树和右子树是否都满足二叉搜索树
return dfs(root.left, lower, root.val) && dfs(root.right, root.val, upper);
}
}
- 时间复杂度:O(n) 其中 n 是二叉树节点的个数。在递归调用时二叉树的每个节点最多被访问一次。因此时间复杂度是O(n)
- 空间复杂度:O(n) 其中 n 是二叉树节点的个数。栈中最多存储 n 个节点变量,所以空间复杂度是O(n)
迭代
public boolean iterate(TreeNode root) {
Deque<TreeNode> stack = new LinkedList<>();
long pred = Long.MIN_VALUE;
while (!stack.isEmpty() || root != null) {
//遍历到左子树尽头
while (root != null) {
stack.push(root);
root = root.left;
}
root = stack.poll();
//中序遍历二叉搜索树得到的是升序数组。root小于等于前驱节点说明不是二叉搜索树
if (root.val <= pred) return false;
pred = root.val;
root = root.right;
}
return true;
}
二叉搜索树迭代器
实现一个二叉搜索树迭代器。你将使用二叉搜索树的根节点初始化迭代器。
调用 next() 将返回二叉搜索树中的下一个最小的数。
示例:
BSTIterator iterator = new BSTIterator(root);
iterator.next(); // 返回 3
iterator.next(); // 返回 7
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 9
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 15
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 20
iterator.hasNext(); // 返回 false
提示:
next() 和 hasNext() 操作的时间复杂度是 O(1),并使用 O(h) 内存,其中 h 是树的高度。 你可以假设 next() 调用总是有效的,也就是说,当调用 next() 时,BST 中至少存在一个下一个最小的数
链接:leetcode-cn.com/leetbook/re… 来源:力扣(LeetCode)
class BSTIterator {
Deque<TreeNode> stack;
public BSTIterator(TreeNode root) {
stack = new LinkedList<>();
help(root);
}
// root及左子树入栈
private void help(TreeNode root) {
while (root != null) {
stack.push(root);
root = root.left;
}
}
public int next() {
TreeNode root = stack.poll();
// 如果右子树不为空,右子树入栈
if (root.right != null) {
help(root.right);
}
return root.val;
}
public boolean hasNext() {
return stack.size() > 0;
}
}
- 时间复杂度:O(n) 其中 n 是二叉树节点个数。需要遍历所有二叉树节点入栈,所以时间复杂度是 O(n)
- 空间复杂度:O(n) 其中 n 是二叉树节点个数。在单调二叉树的最坏情况下,栈中存储需要存储所有二叉树节点。空间复杂度是O(n)
二叉搜索树的基本操作
二叉树中的搜索
给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL。
例如,
给定二叉搜索树:
4
/ \
2 7
/ \
1 3
和值: 2 你应该返回如下子树:
2
/ \
1 3
在上述示例中,如果要找的值是 5,但因为没有节点值为 5,我们应该返回 NULL。
链接:leetcode-cn.com/leetbook/re… 来源:力扣(LeetCode)
迭代
public TreeNode searchBST(TreeNode root, int val) {
while (root != null) {
if (root.val == val) break;
root = val < root.val ? root.left : root.right;
}
return root;
}
- 平均时间复杂度:O(logn),最坏时间复杂度:O(n)。
- 空间复杂度:O(1)
递归
public TreeNode searchBST(TreeNode root, int val) {
if (root == null || root.val == val) return root;
return root.val > val ? searchBST(root.left, val) : searchBST(root.right, val);
}
- 平均时间复杂度:O(logn),最坏时间复杂度:O(n)。
- 空间复杂度:O(1)
二叉树中的插入
给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
示例 1:
输入:root = [4,2,7,1,3], val = 5
输出:[4,2,7,1,3,5]
示例 2:
输入:root = [40,20,60,10,30,50,70], val = 25 输出:[40,20,60,10,30,50,70,null,null,25]
链接:leetcode-cn.com/leetbook/re… 来源:力扣(LeetCode)
递归
public TreeNode insertIntoBST(TreeNode root, int val) {
// 空树-创建新节点
if (root == null) return new TreeNode(val);
if (root.val > val) {
// 左子树中创建新节点,并返回新的root
root.left = insertIntoBST(root.left, val);
} else if (root.val < val) {
// 右子树中创建新节点,并返回新的root
root.right = insertIntoBST(root.right, val);
}
return root;
}
- 时间复杂度:O(n) 其中 n 是二叉树节点个数。最坏情况下我们需要将节点插入树的最深叶子节点,而叶子节点最深是O(n) 。
- 空间复杂度:O(1) 只是用到了常数大小的空间。
迭代
public TreeNode insertIntoBST(TreeNode root, int val) {
// 空树-创建新节点
if (root == null) return new TreeNode(val);
TreeNode node = root;
while (node != null) {
if (val < node.val) {
// 左子树为空-新建节点做为左子树
if (node.left == null) {
node.left = new TreeNode(val);
break;
} else {
// 迭代左子树寻找插入位置
node = node.left;
}
} else {
// 右子树为空-新建节点做为右子树
if (node.right == null) {
node.right = new TreeNode(val);
break;
} else {
// 迭代右子树寻找插入位置
node = node.right;
}
}
}
return root;
}
- 时间复杂度:O(n) 其中 n 是二叉树节点个数。最坏情况下我们需要将节点插入树的最深叶子节点,而叶子节点最深是O(n) 。
- 空间复杂度:O(1) 只是用到了常数大小的空间。
二叉树中的删除
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
首先找到需要删除的节点; 如果找到了,删除它。 说明: 要求算法时间复杂度为 O(h),h 为树的高度。
示例:
root = [5,3,6,2,4,null,7]
key = 3
5
/ \
3 6
/ \ \
2 4 7
给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。
一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。
5
/ \
4 6
/ \
2 7
另一个正确答案是 [5,2,6,null,4,null,7]。
5
/ \
2 6
\ \
4 7
链接:leetcode-cn.com/leetbook/re… 来源:力扣(LeetCode)
删除节点分为三种情况:
- 删除的是叶子节点,直接删除即可
- 删除的是非叶子结点且只有一个孩子节点,将孩子节点和删除节点做替换
- 删除的是非叶子结点且有两个孩子节点,可以选择它中序的前驱结点或后继节点来替换,然后删除目标节点。
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
if (root == null) return root;
if (root.val > key) {
// 目标节点在左子树中,递归到左子树删除该节点。重新赋值root.left因为有可能root.left被删除了
root.left = deleteNode(root.left, key);
} else if (root.val < key) {
root.right = deleteNode(root.right, key);
} else {
if (root.left == null && root.right == null) {
// 删除的是叶子节点root赋值null即可
root = null;
} else if (root.left != null) {
// 目标节点左孩子不为空,查找中序前驱节点赋值给root.val,然后左子树中删除找到的前驱结点完成替换。
root.val = predcessor(root);
root.left = deleteNode(root.left, root.val);
} else {
// 情况2 目标节点只有一个右孩子节点。
//查找中序后继节点赋值给root.val,然后再右子树中删除找到的后继节点完成替换
root.val = successor(root);
root.right = deleteNode(root.right, root.val);
}
}
return root;
}
/**返回前驱节点值*/
public int predcessor(TreeNode root) {
root = root.left;
while (root.right != null) {
root = root.right;
}
return root.val;
}
/**返回后继节点值*/
public int successor(TreeNode root) {
root = root.right;
while (root.left != null) {
root = root.left;
}
return root.val;
}
}