性质:
- 当前节点的左子树中的任何一个节点的权值 < 当前节点的权值
- 当前节点的右子树中的任何一个节点的权值 > 当前节点的权值
如果有相同的值多次出现,用一个计数器统计即可。
插入
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;} elsedfs(root.left, val);} else {if (root.right == null) {root.right = new TreeNode(val);return;} elsedfs(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);elsereturn 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 { //当前节点值等于valif (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);elseadd(root.left, val);} else if (val > root.val) {if (root.right == null)root.right = new TreeNode(val);elseadd(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);elsereturn 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);elsereturn 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);*/
