性质:
- 当前节点的左子树中的任何一个节点的权值 < 当前节点的权值
- 当前节点的右子树中的任何一个节点的权值 > 当前节点的权值
如果有相同的值多次出现,用一个计数器统计即可。
插入
701. 二叉搜索树中的插入操作
给定二叉搜索树(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]
示例 3:
输入:root = [4,2,7,1,3,null,null,null,null,null,null], val = 5
输出:[4,2,7,1,3,5]
提示:
- 给定的树上的节点数介于
0
和10^4
之间 - 每个节点都有一个唯一整数值,取值范围从
0
到10^8
-10^8 <= val <= 10^8
- 新值和原始二叉搜索树中的任意节点值都不同
思路:
- 若树为空,将该值作为根节点插入
- 树不为空
- 若val < root.val,
- 左子树为空,插入到当前节点的左节点
- 递归在左子树中寻找插入位置
- 若val > root.val
- 右子树为空,插入到当前节点的右节点
- 递归在右子树中寻找插入位置
- 若val < root.val,
时间复杂度:O(h),h为二叉搜索树的最大高度
空间复杂度:取决于递归法还是迭代法
//递归
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null)
return new TreeNode(val);
dfs(root, val);
return root;
}
void dfs(TreeNode root, int val) {
if (root.val > val) {
if (root.left == null) {
root.left = new TreeNode(val);
return;
} else
dfs(root.left, val);
} else {
if (root.right == null) {
root.right = new TreeNode(val);
return;
} else
dfs(root.right, val);
}
}
}
//迭代
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null)
return new TreeNode(val);
TreeNode res = root;
while (true) {
if (root.val > val) {
if (root.left == null) {
root.left = new TreeNode(val);
return res;
} else {
root = root.left;
}
} else {
if (root.right == null) {
root.right = new TreeNode(val);
return res;
} else {
root = root.right;
}
}
}
}
}
查找
700. 二叉搜索树中的搜索
给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL。
例如,
给定二叉搜索树:
4<br /> / \<br /> 2 7<br /> / \<br /> 1 3
和值: 2
你应该返回如下子树:
2
/ \
1 3
在上述示例中,如果要找的值是 5
,但因为没有节点值为 5
,我们应该返回 NULL
。
思路:
利用二叉搜索树的性质
- 如果当前节点为空,说明没找到,返回null
- 如果当前节点val == val,返回当前节点
- 如果当前节点val > val,在左子树中寻找
- 如果当前节点val < val,在右子树中找
时间复杂度:O(h),h为二叉搜索树的最大高度
空间复杂度:取决于递归法还是迭代法
//递归
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
if (root == null)
return null;
if (root.val == val)
return root;
else if (root.val > val)
return searchBST(root.left,val);
else
return searchBST(root.right, val);
}
}
//迭代
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
while (root != null && root.val != val)
root = root.val > val ? root.left : root.right;
return root;
}
}
删除
450. 删除二叉搜索树中的节点
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
- 首先找到需要删除的节点;
如果找到了,删除它。
示例 1:
输入:root = [5,3,6,2,4,null,7], key = 3输出:[5,4,6,2,null,null,7]解释:给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。 一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。 另一个正确答案是 [5,2,6,null,4,null,7]。
示例 2:
输入: root = [5,3,6,2,4,null,7], key = 0
输出: [5,3,6,2,4,null,7]
解释: 二叉树不包含值为 0 的节点
示例 3:
输入: root = [], key = 0
输出: []
提示:
- 节点数的范围
[0, 104]
. -105 <= Node.val <= 105
- 节点值唯一
root
是合法的二叉搜索树-105 <= key <= 105
进阶: 要求算法时间复杂度为 O(h),h 为树的高度。
思路:
首先得知道二叉搜索树在中序遍历时的前驱和后继的概念
前驱指中序遍历排在当前点前一个位置的节点,后继指中序遍历排在当前点后一个位置的节点。
对于当前根节点来说,如果它的左子树不为空,则它的前驱节点为其左节点的最右侧节点,否则看其父节点,找到第一个左拐点,如果没有,说明当前节点不存在前驱,是全局最小值。
对于当前根节点来说,如果它的右子树不为空,则它的后继节点为其右子树的最左侧节点,否则看其父节点找到第一个右拐点,如果没有,说明当前节点不存在后继,是全局最大值。
对于本题来讲
- 若不存在待删除节点,返回根节点即可
- 若根节点值等于待删除值
- 若根节点既无前驱也无后继,返回null
- 若根节点有后继节点,用后继节点的值替换根节点,递归右子树删除后继节点,返回根节点
- 若根节点有前驱节点,用前驱节点的值替换根节点,递归左子树删除前驱节点。返回根节点
- 若根节点值大于待删除值,root.left = 递归在左子树中删除,返回根节点
- 若根节点值小于待删除值,root.right = 递归在右子树中删除,返回根节点
时间复杂度为 O(h),h 为树的高度
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
if (root == null)
return null;
if (root.val > key)
root.left = deleteNode(root.left, key);
else if (root.val < key)
root.right = deleteNode(root.right, key);
else { //当前节点值等于val
if (root.left == null && root.right == null)
root = null;
else if (root.right != null) {
root.val = getSuccessor(root);
root.right = deleteNode(root.right, root.val);
} else {
root.val = getPredecessor(root);
root.left = deleteNode(root.left, root.val);
}
}
return root;
}
int getPredecessor(TreeNode root) {
TreeNode cur = root.left;
while (cur.right != null)
cur = cur.right;
return cur.val;
}
int getSuccessor(TreeNode root) {
TreeNode cur = root.right;
while (cur.left != null)
cur = cur.left;
return cur.val;
}
}
一个综合题
220. 存在重复元素 III
给你一个整数数组 nums 和两个整数 k 和 t 。请你判断是否存在 两个不同下标 i 和 j,使得 abs(nums[i] - nums[j]) <= t ,同时又满足 abs(i - j) <= k 。
如果存在则返回 true,不存在返回 false。
示例 1:
输入:nums = [1,2,3,1], k = 3, t = 0 输出:true
示例 2:
输入:nums = [1,0,1,1], k =_ _1, t = 2 输出:true
示例 3:
输入:nums = [1,5,9,1,5,9], k = 2, t = 3 输出:false
提示:
- 0 <= nums.length <= 2 * 104
- -2^31 <= nums[i] <= 2^31 - 1
- 0 <= k <= 104
- 0 <= t <= 2^31 - 1
思路:
如果本题使用二叉搜索树维护窗口,需要用到二叉搜索树的插入,删除以及查询操作(这里的查询是一种区间内的查询,区间中的任意一点都可以,也就是寻找最近公共祖先)。
更进一步:二叉搜索树由于本身不具有自动平衡的特性,所以使用红黑树效率更高,也就是Java的TreeSet
数据结构
还有一种做法是使用桶排序
,很巧妙的方法。
//二叉树做法(又臭又长)
class Solution {
class TreeNode {
int val;
TreeNode left, right;
TreeNode(int val) {
this.val = val;
}
}
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
TreeNode root = null;
for (int i = 0; i < nums.length; i++) {
if (i - k - 1 >= 0)
root = delete(root, nums[i - k - 1]);
Integer large = 1L * t + nums[i] > Integer.MAX_VALUE ? Integer.MAX_VALUE : t + nums[i];
Integer small = nums[i] - 1L * t < Integer.MIN_VALUE ? Integer.MIN_VALUE : nums[i] - t;
TreeNode res = find(root, large, small);
if (res != null && res.val >= small && res.val <= large)
return true;
root = add(root, nums[i]);
}
return false;
}
TreeNode delete(TreeNode root, int key) {
if (root == null)
return null;
if (key < root.val)
root.left = delete(root.left, key);
else if (key > root.val)
root.right = delete(root.right, key);
else {
TreeNode ne = getNext(root);
if (ne == null) return root.left;
else {
root.val = ne.val;
root.right = delete(root.right, root.val);
}
}
return root;
}
TreeNode getNext(TreeNode root) {
if (root == null || root.right == null)
return null;
root = root.right;
while (root.left != null)
root = root.left;
return root;
}
TreeNode add(TreeNode root, int val) {
if (root == null)
return new TreeNode(val);
if (val < root.val) {
if (root.left == null)
root.left = new TreeNode(val);
else
add(root.left, val);
} else if (val > root.val) {
if (root.right == null)
root.right = new TreeNode(val);
else
add(root.right, val);
}
return root;
}
TreeNode find(TreeNode root, int p, int q) {
if (root == null)
return null;
if (p > q)
return find(root, q, p);
if (p > root.val)
return find(root.right, p, q);
else if (q < root.val)
return find(root.left, p, q);
else
return root;
}
}
其余方法见存在重复元素
扩展
带邻域的二叉搜索树能完成更多任务。
例如,每个节点的一个邻域为以当前节点为根节点的子树的总节点数。
703. 数据流中的第 K 大元素
设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。
请实现 KthLargest 类:
KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。
示例:
输入:
[“KthLargest”, “add”, “add”, “add”, “add”, “add”]
[[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]]
输出:
[null, 4, 5, 5, 8, 8]
解释:
KthLargest kthLargest = new KthLargest(3, [4, 5, 8, 2]);
kthLargest.add(3); // return 4
kthLargest.add(5); // return 5
kthLargest.add(10); // return 5
kthLargest.add(9); // return 8
kthLargest.add(4); // return 8
提示:
1 <= k <= 10^4
0 <= nums.length <= 10^4
-10^4 <= nums[i] <= 10^4
-10^4 <= val <= 10^4
最多调用 add 方法 10^4 次
题目数据保证,在查找第 k 大元素时,数组中至少有 k 个元素
思路:
方法1:使用带邻域的二叉树,更具有普适性!
方法2:使用优先队列
//方法1:
class KthLargest {
class TreeNode {
int val, cnt;
TreeNode left, right;
TreeNode (int val) {
this.val = val;
}
TreeNode(int val, int cnt) {
this.val = val;
this.cnt = cnt;
}
}
TreeNode root;
int k;
public KthLargest(int k, int[] nums) {
this.k = k;
for (int x : nums)
this.root = add(this.root, x);
}
private TreeNode add(TreeNode cur, int x) {
if (cur == null)
return new TreeNode(x, 1);
if (cur.val == x) {
int lcnt = cur.left == null ? 1 : cur.left.cnt + 1;
TreeNode newNode = new TreeNode(x, lcnt);
newNode.left = cur.left;
cur.left = newNode;
cur.cnt++;
} else if (x < cur.val) {
cur.left = add(cur.left, x);
cur.cnt = cur.right != null ? cur.right.cnt + cur.left.cnt + 1 : cur.left.cnt + 1;
} else if (x > cur.val) {
cur.right = add(cur.right, x);
cur.cnt = cur.left != null ? cur.left.cnt + cur.right.cnt + 1 : cur.right.cnt + 1;
}
return cur;
}
public int add(int val) {
this.root = add(this.root, val);
return find (this.root, this.k);
}
int find (TreeNode cur, int cnt) {
int cc = cur.right != null ? cur.right.cnt + 1 : 1;
if (cc == cnt)
return cur.val;
else if (cc > cnt)
return find(cur.right, cnt);
else
return find(cur.left, cnt - cc);
}
// private void print(TreeNode cur) {
// if (cur == null)
// return ;
// print(cur.left);
// System.out.print(cur.val + " " + cur.cnt + ", ");
// print(cur.right);
// }
}
/**
* Your KthLargest object will be instantiated and called as such:
* KthLargest obj = new KthLargest(k, nums);
* int param_1 = obj.add(val);
*/
class KthLargest {
PriorityQueue<Integer> q = new PriorityQueue<>();
int k;
public KthLargest(int k, int[] nums) {
this.k = k;
for (int x : nums)
q.offer(x);
}
public int add(int val) {
q.offer(val);
while (q.size() > k)
q.poll();
return q.peek();
}
}
/**
* Your KthLargest object will be instantiated and called as such:
* KthLargest obj = new KthLargest(k, nums);
* int param_1 = obj.add(val);
*/